上古时期(大雾)的数据结构pdf
分塊+點分治+Treap
byWYCby\ WYCby?WYC
Part1 分塊
概念
就是將nnn個數分成若干個塊,然后要處理的時候整塊一起的加上局部的直接暴力。
如果將塊的大小分配好一般每次都是O(n)O(\sqrt n)O(n?)的。
而且因為十分暴力,所以有很多優秀的性質。
實現方法
怎么暴力的還用講實現方法,好吧,講一些基礎的
要求區間求和,區間修改
對于每個塊維護一個所以值的和和一個懶惰
區間求和時,將整塊的直接加上去,局部的暴力加上去
區間修改時,將整塊的懶惰標記,局部的暴力加上去
對于局部修改時,如果有懶惰就直接暴力將整個塊都修改,然后去掉懶惰。
時間復雜度每個都是O(n)O(\sqrt n)O(n?)
是不是十分優秀
例題
在例題中體現優秀的性質
P4168-[Violet]蒲公英
題目大意
詢問區間眾數
解題思路
將數字離散化,然后分塊。對于數組vi,j,kv_{i,j,k}vi,j,k?,表示i~ji\sim ji~j個塊,kkk的個數。對于詢問(l,r)(l,r)(l,r),將整塊的直接累計,然后局部的直接暴力。
時間復雜度:O(NT2+MNT)O(NT^2+\frac{MN}{T})O(NT2+TMN?),根據LYD的說法,讓T≈n3T\approx \sqrt[3]nT≈3n?的話,時間復雜度就可以在O(N53)O(N^{\frac{5}{3}})O(N35?)級別。
代碼寫優美些或者加點優化就可以過。
code
#include<cstdio> #include<cmath> #include<algorithm> #include<cstring> #define Tn 40 #define N 40010 using namespace std; int n,m,L[Tn],R[Tn],v[Tn][Tn][N],l,r,num[N]; int z[N],w[N],a[N],cnt,k[N],t,T,pos[N],x; bool cmp(int x,int y)//排序 {return z[x]<z[y];} void begins()//預處理 {for(int i=1;i<=t;i++){L[i]=(i-1)*T+1;R[i]=i*T;}if(R[t]<n) t++,L[t]=R[t-1]+1,R[t]=n;//計算邊界for(int i=1;i<=t;i++)for(int j=i;j<=t;j++)for(int k=L[i];k<=R[j];k++)v[i][j][a[k]]++;//計算v數組 } int ask(int l,int r) {int p=pos[l],q=pos[r];if(p-q<=2){memset(k,0,sizeof(k));for(int i=l;i<=r;i++)k[a[i]]++;}else{p++;q--;for(int i=1;i<=cnt;i++)k[i]=v[p][q][i];//直接累計for(int i=l;i<L[p];i++)//暴力統計k[a[i]]++;for(int i=R[q]+1;i<=r;i++)//暴力統計*2k[a[i]]++;}int mark=1;for(int i=2;i<=cnt;i++)//找眾數if(k[i]>k[mark]) mark=i;return w[mark]; } int main() {scanf("%d%d",&n,&m);t=(int)pow(double(n),1.0/3);T=n/t;for(int i=1;i<=n;i++){scanf("%d",&z[i]);num[i]=i;}sort(num+1,num+1+n,cmp);z[0]=-1;for(int i=1;i<=n;i++)//離散化{if(z[num[i]]!=z[num[i-1]]) cnt++,w[cnt]=z[num[i]];;a[num[i]]=cnt;}T=n/t;begins();for(int i=1;i<=m;i++){scanf("%d%d",&l,&r);l=(l+x-1)%n+1;r=(r+x-1)%n+1;if(l>r) swap(l,r);printf("%d\n",x=ask(l,r));} }優秀性質1
對于塊你可以構建二維的,就十分優秀。
如這道題中vi,j,kv_{i,j,k}vi,j,k?對于塊進行了二維
P4879-ycz的妹子
題目大意
有若干種操作
解題思路
我們發現只有單點修改,和全部求和。所以ansansans表示全部的和,然后前三個操作就搞定了。
第四個操作維護一個分塊,記錄每塊有的數的個數,然后找到目標在那個塊,之后這個塊里暴力尋找,這個操作時間負責度O(n)O(\sqrt n)O(n?)
總體時間復雜度:O(nn)O(n\sqrt n)O(nn?)
codecodecode
#include<cstdio> #include<cmath> #include<iostream> #define ll long long #define N 500000 #define T 1000 using namespace std; ll n,m,a[N+10],ans,no[N+10],x,y,t; ll L[T],R[T],num[T],pos[N+10]; char c; int main() {scanf("%lld%lld",&n,&m);t=sqrt(N);for(ll i=1;i<=N;i++){pos[i]=(i-1)/t+1;if(i>n)no[i]=1;}for(ll i=1;i<=n;i++){scanf("%lld",&a[i]);num[pos[i]]++;ans+=a[i];}for(ll i=1;i<=m;i++){cin>>c;if(c=='Q')printf("%lld\n",ans);else if(c=='I'){scanf("%lld%lld",&x,&y);ans-=a[x];a[x]=y;ans+=y;n=max(n,x);if(no[x]){no[x]=0;num[pos[x]]++;}}else if(c=='C'){scanf("%lld%lld",&x,&y);if(no[x]) continue;ans-=y;a[x]-=y;}else if(c=='D'){scanf("%lld",&x);ll k=1;while(x-num[k]>0)x-=num[k],k++;for(int i=(k-1)*t+1;i<=min(k*t,n);i++){if(!no[i]) --x;if(!x){num[k]--;no[i]=1;ans-=a[i];a[i]=0;break;}}}} }優秀性質2
一個塊一個塊的跑,利用儲存塊的信息快速鎖定目標
P3203-[HNOI2010]彈飛綿羊
這個就強大了
題目大意
nnn個裝置。到第iii個裝置會被往前彈aia_iai?個。
兩種操作
修改aia_iai?和詢問從iii出發要多少次彈射可以彈出去。
解題思路
first,本題正解LCT。然而分塊可以輕松過掉
分塊。對于每個點,維護要多少步彈出該塊和彈出去后彈到哪里。
詢問就直接根據兩個數據,修改就直接重構整個塊。
時間復雜度:O(nn)O(n\sqrt n)O(nn?)
code
#include<cstdio> #include<cmath> #define N 200010 #define T 500 using namespace std; int n,m,x,t,a[N],L[T],R[T],step[N],to[N],pos[N]; void pre_work()//預處理 {for(int i=1;i<=t;i++)//塊邊界{L[i]=(i-1)*t+1;R[i]=i*t;}if(R[t]!=n) t++,L[t]=R[t-1]+1,R[t]=n;for(int i=1;i<=t;i++)for(int j=R[i];j>=L[i];j--){if(j+a[j]<=R[i]) step[j]=step[j+a[j]]+1,to[j]=to[j+a[j]];else step[j]=1,to[j]=j+a[j];pos[j]=i;}//初始數據 } int ask(int x)//詢問 {int ans=0;while(x<=n)ans+=step[x],x=to[x];return ans; } void change(int i)//重構塊 {for(int j=R[i];j>=L[i];j--)if(j+a[j]<=R[i]) step[j]=step[j+a[j]]+1,to[j]=to[j+a[j]];else step[j]=1,to[j]=j+a[j]; } int main() {scanf("%d",&n);t=sqrt(n);for(int i=1;i<=n;i++)scanf("%d",&a[i]);pre_work();scanf("%d",&m);for(int i=1;i<=m;i++){scanf("%d",&x);if(x==1){scanf("%d",&x);x++;printf("%d\n",ask(x));}else{scanf("%d",&x);x++;scanf("%d",&a[x]);change(pos[x]);}} }優秀性質3
本題最強大的地方是在于當他修改時,直接暴力重構了整個塊,十分強大
性質總結
對于分塊,最強大的操作就是暴力。因為無論怎么暴力,只要每次保證不用重構太多塊,基本都是O(n)O(\sqrt n)O(n?)的
然后就算根據每個塊的信息找一遍,也依據是O(n)O(\sqrt n)O(n?)的
而且n2=n\sqrt n^2=nn?2=n所以我們可以猥瑣欲為的將數據當做可以n2n^2n2的來看
分塊-終章
“局部暴力,大段維護”。當然分塊十分簡單,性質也十分優秀,也很好寫。
but!but!but!,時間復雜度終究是O(nn)O(n\sqrt n)O(nn?)的,如果出題人讓你用O(nlogn)O(n\ log\ n)O(n?log?n)也沒有辦法
還是老老實實寫別的數據結構對吧
Part2-點分治
概念
首先,點分治無論在速度還是處理問題方面都會被樹上啟發式合并吊打,而且我也不知道為什么他會被分進數據結構里,但是不重要!
點分治在樹上進行分治,每次將樹分治成若干棵不同的子樹處理。
實現方法
一般用于處理樹上路徑長度的問題
這里舉LuoguLuoguLuogu的模板P3806P3806P3806
一棵樹(無向),mmm個詢問求長度為kkk的路徑存不存在。
對于以ppp為根的子樹
然后考慮路徑種類,其實只有兩種
對于1,我們將路徑分成兩半.
求出根節點里每個點的距離did_idi?,和每個節點位于那個子樹bib_ibi?
要求滿足bx≠byb_x\neq b_ybx??=by?和dx+dy=kd_x+d_y=kdx?+dy?=k
這時候定義函數Calc(p)Calc(p)Calc(p)表示以ppp為根的子樹上上述路徑的條數
而路徑二就直接分治讓子樹再處理一遍就好了。
現在是考慮Calc(p)Calc(p)Calc(p)如何定義的問題。
因為數據很小,用lonxlon_xlonx?表示是否存在長度為xxx的路徑
然后我們對于每顆子樹,先用之前的lonlonlon計算一下詢問
之后在將這棵子樹加入lonlonlon數組
然后在子樹之中繼續處理
時間復雜度:O(TNlogN)O(TN\ log \ N)O(TN?log?N),TTT為樹的層數
可是如果樹是一條鏈,那么時間就會退化為O(N2logN)O(N^2\ log\ N)O(N2?log?N)
但是如果我們每次以子樹的重心作為新分治的根,那么最多只會有logNlog\ Nlog?N層。
時間就可以穩在O(Nlog2N)O(N\ log^2\ N)O(N?log2?N)
Part 3:Treap
概念
TreapTreapTreap就是平衡樹的一種,所謂平衡樹就是要求穩定在lognlog\ nlog?n層左右的BSTBSTBST,為什么要穩定層數,我們等會再講。
實現方法
將TreapTreapTreap的實現方法之前要先將BSTBSTBST
BSTBSTBST
所謂BST,就是二叉查找樹。
二叉查找樹有個優秀的性質,那就是對于每個節點,左子節點的權值比它小,右子節點比它大。
這樣我們就可以愉快的干很多事,比如查找前驅,后繼,排名啊
明人不說暗話,先來將實現方法
開始先構建兩個節點?inf-inf?inf和infinfinf,以任意一個為根
int New(int new_val){val[++tot]=new_val;dat[tot]=rand();cnt[tot]=size[tot]=1;return tot;}void Build(){New(-INF);New(INF);root=1;r[1]=2;Updata(root);}1[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-I4PJVqvd-1576912899795)(https://i.loli.net/2019/01/20/5c446542aac73.png)]
插入andandand刪除:
插入一個數時,先從根開始。對于走到的每一個點,如果比它小就往左,不然就往右走。知道找到一個空余的位置就插入。
void Insert(int &p,int num){if(p==0){p=New(num);return;}//如果有空位了if(num==val[p]){cnt[p]++;Updata(p);return;}//已經有這個數了if(num<val[p]){Insert(l[p],num);}//小于往左else{Insert(r[p],num);}//大于往右Updata(p);//更新數據}刪除也是根據那個走法找到位置,然后刪除
刪除時注意將子節點接上去
void Remove(int val){int &p=root;while(p){if(val==a[p].val) break;p=val<a[p].val?a[p].l:a[p].r;}if(p==0) return;if(a[p].l==0){p=a[p].r}//沒左接右else if(a[p].r==0){p=a[p].l;}//沒右接左else{//求個后繼int next=a[p].r;while(a[next].l>0) next=a[next].l;//next沒有左子樹,直接刪除Romove(a[next].val);//令節點next代替節點p的位置a[next].l=a[p].r;a[next].r=a[p].r;p=next;} }求前驅/后繼
valvalval后繼就是比valvalval大的最小的數。
對于求后繼,我們可以用一步一步往下走的方法,并在路上更新答案ansansans,直到走到valvalval的位置或結束時。
這時有3種結果
前驅同理
int GetPre(int num){int ans=1;int p=root;while(p){if(num==val[p]){//找到val節點if(l[p]>0){p=l[p];//往左一步while(r[p]>0) p=r[p];//一直往右ans=p;//統計}break;}if(val[p]<num&&val[p]>val[ans]) ans=p;p=num<val[p]?l[p]:r[p];//走}return val[ans];}int GetNext(int num){int ans=2;int p=root;while(p){if(num==val[p]){if(r[p]>0){p=r[p];while(l[p]>0) p=l[p];ans=p;}break;}if(val[p]>num&&val[p]<val[ans]) ans=p;p=num<val[p]?l[p]:r[p];}return val[ans];}查詢一個數的排名/一個排名的數
一個數的排名就直接走到那個點,順路根據樹的大小統計前面有多少個點就好了。
一個排名的數,就根據子樹大小來走就ok了。
int GetRankByVal(int p,int num){if(p==0) return 0;if(num==val[p]) return size[l[p]]+1;if(num<val[p]) return GetRankByVal(l[p],num);return GetRankByVal(r[p],num)+size[l[p]]+cnt[p];}int GetValByRank(int p,int rank){if (p==0) return INF;if(size[l[p]]>=rank) return GetValByRank(l[p],rank);if(size[l[p]]+cnt[p]>=rank) return val[p];return GetValByRank(r[p],rank-size[l[p]]-cnt[p]);}這就是BSTBSTBST的全部了。我們來考慮它的弊端。
每次時間就會為O(T)O(T)O(T)
如果我以單調的插入數,那么每次就會退化為O(N)O(N)O(N)
那么我們就要讓這個BSTBSTBST平衡
TreapTreapTreap
TreapTreapTreap是其中一種讓BSTBSTBST保持在lognlog\ nlog?n層左右的一個數據結構
具體實現方法就是通過旋轉,對于一個東西
可以旋轉為
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-dTwPYOyz-1576912899796)(https://i.loli.net/2019/01/20/5c446ed9d8f17.png)]
總之就是分為左旋和右旋
void zig(int &p){int q=l[p];l[p]=r[q];r[q]=p;p=q;Updata(r[p]);Updata(p);}void zag(int &p){int q=r[p];r[p]=l[q];l[q]=p;p=q;Updata(l[p]);Updata(p);}只會我們對每個點,我們定義一個特征值。然后我們通過旋轉讓特征值滿足二叉堆的性質。
只要讓特征值時隨機的,基本都可以維持在lognlog\ nlog?n層附近。
好了,放codecodecode
#include<cstdio> #include<algorithm> #define INF 2147483647/3 #define N 100010 using namespace std; int n,root; struct Treap_node{int l[N],r[N];int val[N],dat[N];int cnt[N],size[N];int tot;int New(int new_val){val[++tot]=new_val;dat[tot]=rand();cnt[tot]=size[tot]=1;return tot;}void Updata(int p){size[p]=size[l[p]]+size[r[p]]+cnt[p];}void Build(){New(-INF);New(INF);root=1;r[1]=2;Updata(root);}int GetRankByVal(int p,int num){if(p==0) return 0;if(num==val[p]) return size[l[p]]+1;if(num<val[p]) return GetRankByVal(l[p],num);return GetRankByVal(r[p],num)+size[l[p]]+cnt[p];}int GetValByRank(int p,int rank){if (p==0) return INF;if(size[l[p]]>=rank) return GetValByRank(l[p],rank);if(size[l[p]]+cnt[p]>=rank) return val[p];return GetValByRank(r[p],rank-size[l[p]]-cnt[p]);}void zig(int &p){int q=l[p];l[p]=r[q];r[q]=p;p=q;Updata(r[p]);Updata(p);}void zag(int &p){int q=r[p];r[p]=l[q];l[q]=p;p=q;Updata(l[p]);Updata(p);}void Insert(int &p,int num){if(p==0){p=New(num);return;}if(num==val[p]){cnt[p]++;Updata(p);return;}if(num<val[p]){Insert(l[p],num);if(dat[p]<dat[l[p]]) zig(p);}else{Insert(r[p],num);if(dat[p]<dat[r[p]]) zag(p);}Updata(p);}int GetPre(int num){int ans=1;int p=root;while(p){if(num==val[p]){if(l[p]>0){p=l[p];while(r[p]>0) p=r[p];ans=p;}break;}if(val[p]<num&&val[p]>val[ans]) ans=p;p=num<val[p]?l[p]:r[p];}return val[ans];}int GetNext(int num){int ans=2;int p=root;while(p){if(num==val[p]){if(r[p]>0){p=r[p];while(l[p]>0) p=l[p];ans=p;}break;}if(val[p]>num&&val[p]<val[ans]) ans=p;p=num<val[p]?l[p]:r[p];}return val[ans];}void Remove(int &p,int num){if(p==0) return;if(num==val[p]){if(cnt[p]>1){cnt[p]--;Updata(p);return;}if(l[p]||r[p]){if(r[p]==0||dat[l[p]]>dat[r[p]])zig(p),Remove(r[p],num);elsezag(p),Remove(l[p],num);Updata(p);}else p=0;return;}num<val[p]?Remove(l[p],num):Remove(r[p],num);Updata(p);} }a; int main() {a.Build();scanf("%d",&n);while(n--){int opt,x;scanf("%d%d",&opt,&x);switch(opt){case 1:a.Insert(root,x);break;case 2:a.Remove(root,x);break;case 3:printf("%d\n",a.GetRankByVal(root,x)-1);break;case 4:printf("%d\n",a.GetValByRank(root,x+1));break;case 5:printf("%d\n",a.GetPre(x));break;case 6:printf("%d\n",a.GetNext(x));break;}} }雖然基本都可以用setsetset搞定,但是還是可以做很多NBNBNB的好東西的。
Part4:樹鏈剖分
概念
將樹分割成若干條鏈來處理,將樹上的問題轉換為線性問題
實現方法
實現概念
學樹鏈剖分之前我們先來getgetget一些前置知識
重兒子:子樹最大的一個兒子
輕兒子:其他兒子
重邊:連接重兒子的邊
輕邊:其他邊
重路徑:重邊的路徑
還有一個重要性質:從根到任何一個點的輕邊數不超過lognlog\ nlog?n條
證明:
若走到一條輕邊,那么子樹大小至少縮小一半,那么最多走log?n\log nlogn條輕邊子樹大小就會縮小為1
證畢
所以我們對于一條路徑的輕邊數量也是logloglog級的。
所以我們可以對于重路徑將樹進行劃分。
我們來看例題:
要求支持子樹修改,子樹求和,路徑修改,路徑求和。
對于子樹操作很簡單,我們按照dfsdfsdfs序加入線段樹,然后線段樹區間修改就可以做到子樹修改,可以路徑問題如何解決?
這時候我們就可以用樹鏈剖分了。我們考慮將dfsdfsdfs修改一下。
我們可以每次優先走重兒子,那么重路徑在線段樹上的位置就是連續的,對于一段重路徑我們就可以進行區間修改了,而輕邊有不會太多,所以一次操作時間復雜度是O(log2n)O(log^2\ n)O(log2?n)的
代碼實現
我們先跑兩次dfsdfsdfs預處理
第一遍:
sonx:xson_x:xsonx?:x的重兒子
depx:xdep_x:xdepx?:x的深度
sizx:x?siz_x:x?sizx?:x?的子樹大小
fx:xf_x:xfx?:x的父節點
第二遍:
segx:x?seg_x:x?segx?:x?在線段樹上的位置
idz:id_z:idz?:線段樹zzz位置上的節點
topx:xtop_x:xtopx?:x所在重路徑最淺的節點
之后我們就預處理完了。
之后我們對于路徑操作可以向倍增求LCALCALCA一樣,將一條路徑拆分為兩條路徑,不過變為每次跳到topxtop_xtopx?,然后跳的過程重進行用線段樹進行區間修改。
上
codecodecode
#include<cstdio> #include<algorithm> using namespace std; const int N=200000; int tot,cnt,n,m,s,p,ls[N],pw[N],id[N]; int siz[N],dep[N],f[N],son[N],seg[N],top[N]; struct treenode{int l,r,lazy,val; }; struct LineTree{treenode t[N*2];void build(int x,int l,int r){t[x].l=l;t[x].r=r;if(l==r){t[x].val=pw[id[l]]%p;return;}int mid=(l+r)>>1;build(x*2,l,mid);build(x*2+1,mid+1,r);t[x].val=(t[x*2].val+t[x*2+1].val)%p;}void downdata(int x){if(t[x].lazy){(t[x*2].lazy+=t[x].lazy)%=p;(t[x*2].val+=t[x].lazy*(t[x*2].r-t[x*2].l+1))%=p;(t[x*2+1].lazy+=t[x].lazy)%=p;(t[x*2+1].val+=t[x].lazy*(t[x*2+1].r-t[x*2+1].l+1))%=p;t[x].lazy=0;}}void change(int x,int l,int r,int num){if(t[x].l==l&&t[x].r==r){(t[x].val+=num*(t[x].r-t[x].l+1))%=p;(t[x].lazy+=num)%=p;return;}downdata(x);int mid=(t[x].l+t[x].r)>>1;if(r<=mid) change(x*2,l,r,num);else if(l>mid) change(x*2+1,l,r,num);else change(x*2,l,mid,num),change(x*2+1,mid+1,r,num);t[x].val=(t[x*2].val+t[x*2+1].val)%p;}int find(int x,int l,int r){if(t[x].l==l&&t[x].r==r)return t[x].val;downdata(x);int mid=(t[x].l+t[x].r)>>1;if(r<=mid) return find(x*2,l,r);else if(l>mid) return find(x*2+1,l,r);else return (find(x*2,l,mid)+find(x*2+1,mid+1,r))%p; } }LT;//線段樹 struct edge_node{int to,next; }a[N*2]; void addl(int x,int y) {a[++tot].to=y;a[tot].next=ls[x];ls[x]=tot; } void dfs1(int x,int fa)//第一遍預處理 {siz[x]=1;for(int i=ls[x];i;i=a[i].next){int y=a[i].to;if(y==fa) continue;dep[y]=dep[x]+1;f[y]=x;dfs1(y,x);siz[x]+=siz[y];if(siz[y]>siz[son[x]])son[x]=y;} } void dfs2(int x,int fa)//第二遍預處理 {seg[x]=++cnt;id[cnt]=x;if(son[x]){top[son[x]]=top[x];dfs2(son[x],x);}for(int i=ls[x];i;i=a[i].next){int y=a[i].to;if(y==fa||y==son[x]) continue;top[y]=y;dfs2(y,x);} } void path_change(int x,int y,int z)//路徑修改 {while(top[x]!=top[y]){if(dep[top[x]]<dep[top[y]]) swap(x,y);//哪個深度大跳哪個LT.change(1,seg[top[x]],seg[x],z);//路徑修改x=f[top[x]];//跳}if(dep[x]>dep[y]) swap(x,y);LT.change(1,seg[x],seg[y],z);return; } int path_ask(int x,int y)//路徑查詢—同上 {int ans=0;while(top[x]!=top[y]){if(dep[top[x]]<dep[top[y]]) swap(x,y);(ans+=LT.find(1,seg[top[x]],seg[x]))%=p;x=f[top[x]];}if(dep[x]>dep[y]) swap(x,y);(ans+=LT.find(1,seg[x],seg[y]))%=p;return ans; } int main() {scanf("%d%d%d%d",&n,&m,&s,&p);for(int i=1;i<=n;i++)scanf("%d",&pw[i]);for(int i=1;i<n;i++){int x,y;scanf("%d%d",&x,&y);addl(x,y);addl(y,x);}dfs1(s,0);top[s]=s;dfs2(s,0);LT.build(1,1,n);for(int i=1;i<=m;i++){int t,x,y,z;scanf("%d%d",&t,&x);if(t==1){scanf("%d%d",&y,&z);path_change(x,y,z);}if(t==2){scanf("%d",&y);printf("%d\n",path_ask(x,y));}if(t==3){scanf("%d",&z);LT.change(1,seg[x],seg[x]+siz[x]-1,z);}if(t==4){printf("%d\n",LT.find(1,seg[x],seg[x]+siz[x]-1));}} }Part 5:練習題
luogu3870?[TJOI2009]luogu3870-[TJOI2009]luogu3870?[TJOI2009]開關【分塊】
luogu4008?[NOI2003]luogu4008-[NOI2003]luogu4008?[NOI2003]文本編輯器【分塊】
luogu2634?[luogu2634-[luogu2634?[國家集訓隊]]]聰聰可可【點分治】
luogu4178?Treeluogu4178-Treeluogu4178?Tree【點分治】
luogu3714?[BJOI2017]luogu3714-[BJOI2017]luogu3714?[BJOI2017]樹的難題【點分治】
luogu3345?[ZJOI2015]?luogu3345-[ZJOI2015]?luogu3345?[ZJOI2015]?幻想鄉戰略游戲【點分治】
luogu1503??luogu1503-?luogu1503??鬼子進村【Treap?Treap?Treap?】
luogu1533?luogu1533-luogu1533?可憐的狗狗【Treap?Treap?Treap?】
luogu3313?[SDOI2014]luogu3313-[SDOI2014]luogu3313?[SDOI2014]旅行【主席樹,,,樹鏈剖分】
luogu2486?[SDOI2011]?luogu2486-[SDOI2011]?luogu2486?[SDOI2011]?染色【樹鏈剖分】
luogu2146?[NOI2015]luogu2146-[NOI2015]luogu2146?[NOI2015]軟件包管理器【樹鏈剖分】
總結
以上是生活随笔為你收集整理的上古时期(大雾)的数据结构pdf的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 免费ddos平台攻击(收费ddos攻击平
- 下一篇: linux图形化界面卡死(linux的图