突然發現網上關於點分和動態點分的教程好像很少……蒟蒻開篇blog記錄一下吧……因為這是個大傻逼,可能有很多地方寫錯,歡迎在下面提出
參考文獻:https://www.cnblogs.com/LadyLex/p/8006488.html
https://blog.csdn.net/qq_39553725/article/details/77542223
https://blog.csdn.net/zzkksunboy/article/details/70244945
前言
一般來說,對於大規模處理樹上路徑,我們會對整棵樹進行分治。而樹分治有兩種,一種是邊分治,不在本文考慮范圍內(主要是我不會)所以暫且不提,另外一種就是點分治。
淀粉質,啊呸,點分治,顧名思義,就是把樹上的節點拆開來進行分治,每一次把樹給拆成好幾棵子樹,然后再繼續遞歸下去,直到算出所有的答案
分治點
既然是分治,我們肯定每一次都要選擇一個點,從他開始分治下去。那么考慮我們如何選擇這個點呢?我們發現,每一次處理完一個點之后,我們都要遞歸進它的子樹,那么時間復雜度受到它最大的子樹的大小的影響。比如,如果原樹是一條鏈,我們選擇鏈首,一路遞歸下去,時間復雜度毫無疑問是$O(n^2)$的(那還不如別學了直接打暴力)。所以,我們要讓每一次選到的點的最大子樹最小。
實際上,一棵樹的最大子樹最小的點有一個名稱,叫做重心。
時間復雜度
考慮一下為什么每一次都選擇重心,時間復雜度就是對的呢?
因為重心有一個很重要的性質,每一個子樹的大小都不超過$n/2$
考慮為什么呢?我們可以用反證法來證明(這里感謝zzk大佬的證明)
考慮有如上這么一棵樹,其中點$u$是重心,$son[u]$表示$u$點的最大的子樹的大小,$v$是點$u$的最大子樹,且$size[v]>size[u]/2$
因為$size[v]>size[u]/2$,其他子樹加上點$u$的節點數小於$size[u]/2$,那么不難發現,我們選擇點$v$作為重心,$son[v]=size[v]-1<son[u]$,那么很明顯$u$不滿足重心的定義
於是每一次找到重心,遞歸的子樹大小是不超過原樹大小的一半的,那么遞歸層數不會超過$O(logn)$層,時間復雜度為$O(nlogn)$
求重心
然而重心如何求呢?直接暴力,我們$dfs$整棵樹,可以$O(n)$的求出樹的重心
還是貼代碼好了
void findrt(int u,int fa){ //sz表示子樹的大小,son表示點的最大子樹的大小 //cmax(a,b)表示如果b>a則a=b //個人習慣這樣寫,或者直接寫成a=max(a,b) sz[u]=1,son[u]=0; for(int i=head[u];i;i=Next[i]){ int v=ver[i]; if(vis[v]||v==fa) continue; findrt(v,u); sz[u]+=sz[v]; cmax(son[u],sz[v]); } //size表示整棵樹的大小 //因為這是一棵無根樹,所以包括它的父親在內的那一坨也應該算作它的子樹 cmax(son[u],size-sz[u]); if(son[u]<mx) mx=son[u],rt=u; }
於是就可以愉快的$O(n)$求出重心(~ ̄▽ ̄)~
實現
還是先貼代碼好了
void divide(int u){ ans+=solve(u,0);//把當前節點的答案加上去 vis[u]=1;//把節點標記,防止陷入死循環 for(int i=head[u];i;i=Next[i]){ //分別處理每一棵子樹 int v=ver[i]; if(vis[v]) continue; ans-=solve(v,edge[i]);//容斥原理,下面說 mx=inf,rt=0,size=sz[v]; //把所有信息更新,遞歸進子樹找重心,並繼續分治 findrt(v,0); divide(rt); } }
上面其他的應該都好理解,除了這一句 ans-=solve(v,edge[i]);
考慮一下這棵樹
考慮一下,從點$1$出發的路徑有以下幾條
$1->4$
$1->4->6$
$1->2$
$1->2->3$
$1->2->5$
然后我們為了求貢獻,會將路徑兩兩合並
然而合並$1->2->3$和$1->2->5$這兩條路徑實際上是不合法的,因為出現了重邊
所以要減去$2$這一棵子樹中的所有路徑兩兩合並的貢獻
然后回頭來看代碼 ans+=solve(u,0); ans-=solve(v,edge[i]);
看到沒?第二個參數不一樣,這樣在考慮子樹中兩兩合並時的貢獻時就不會把這一條邊的貢獻給漏掉了
然后只要遞歸繼續找就可以(*^▽^*)
注意點
實際上呢,點分的時候找重心的寫法有兩種,一種是上面的那個,另一種我貼上來,就是在遞歸的時候寫的不一樣
1 void divide(int u){ 2 ans+=solve(u,0); 3 vis[u]=1; 4 int totsz=size; 5 for(int i=head[u];i;i=Next[i]){ 6 int v=ver[i]; 7 if(vis[v]) continue; 8 ans-=solve(v,edge[i]); 9 mx=inf,rt=0; 10 size=sz[v]>sz[u]?totsz-sz[u]:sz[v];//這里應該這樣寫才是對的 11 findrt(v,0); 12 divide(rt); 13 } 14 }
實際上這一種方法才是對的,我上面寫的反而是錯的,然而時間復雜度並不會退化,具體的證明請看->這里
例題
講了這么多……然而實際上似乎並沒有什么用……還是來講幾道題好了……

//minamoto #include<bits/stdc++.h> #define N 40005 #define M 80005 #define ll long long #define inf 0x3f3f3f3f using namespace std; #define getc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<15,stdin),p1==p2)?EOF:*p1++) char buf[1<<15],*p1=buf,*p2=buf; template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;} inline int read(){ #define num ch-'0' char ch;bool flag=0;int res; while(!isdigit(ch=getc())) (ch=='-')&&(flag=true); for(res=num;isdigit(ch=getc());res=res*10+num); (flag)&&(res=-res); #undef num return res; } int ver[M],Next[M],head[N],edge[M]; int n,tot,root;ll k; void add(int u,int v,int e){ ver[++tot]=v,Next[tot]=head[u],head[u]=tot,edge[tot]=e; ver[++tot]=u,Next[tot]=head[v],head[v]=tot,edge[tot]=e; } int sz[N],vis[N],mx,size; ll d[N],q[N],l,r; void getroot(int u,int fa){ sz[u]=1;int num=0; for(int i=head[u];i;i=Next[i]){ int v=ver[i]; if(v==fa||vis[v]) continue; getroot(v,u); sz[u]+=sz[v]; cmax(num,sz[v]); } cmax(num,size-sz[u]); if(num<mx) mx=num,root=u; } void getdis(int u,int fa){ q[++r]=d[u]; for(int i=head[u];i;i=Next[i]){ int v=ver[i]; if(v==fa||vis[v]) continue; d[v]=d[u]+edge[i]; getdis(v,u); } } ll calc(int u,int val){ r=0; d[u]=val; getdis(u,0); ll sum=0;l=1; sort(q+1,q+r+1); while(l<r){ if(q[l]+q[r]<=k) sum+=r-l,++l; else --r; } return sum; } ll ans=0; void dfs(int u){ ans+=calc(u,0); vis[u]=1; for(int i=head[u];i;i=Next[i]){ int v=ver[i]; if(vis[v]) continue; ans-=calc(v,edge[i]); size=sz[v]; mx=inf; getroot(v,0); dfs(root); } } int main(){ //freopen("testdata.in","r",stdin); n=read(); for(int i=1;i<n;++i){ int u=read(),v=read(),e=read(); add(u,v,e); } k=read(); size=n; mx=inf; getroot(1,0); dfs(root); printf("%lld",ans); return 0; }
poj1741tree
給一顆n個節點的樹,每條邊上有一個距離v。定義d(u,v)為u到v的最小距離。給定k值,求有多少點對(u,v)使u到v的距離小於等於k。
點分的板子……好像基本都是板子套進去……就是注意合並的時候二分保證復雜度
bzoj2152 聰聰可可
題目描述
聰聰和可可是兄弟倆,他們倆經常為了一些瑣事打起來,例如家中只剩下最后一根冰棍而兩人都想吃、兩個人都想玩兒電腦(可是他們家只有一台電腦)……遇到這種問題,一般情況下石頭剪刀布就好了,可是他們已經玩兒膩了這種低智商的游戲。
他們的爸爸快被他們的爭吵煩死了,所以他發明了一個新游戲:由爸爸在紙上畫n個“點”,並用n-1條“邊”把這n個“點”恰好連通(其實這就是一棵樹)。並且每條“邊”上都有一個數。接下來由聰聰和可可分別隨即選一個點(當然他們選點時是看不到這棵樹的),如果兩個點之間所有邊上數的和加起來恰好是3的倍數,則判聰聰贏,否則可可贏。
聰聰非常愛思考問題,在每次游戲后都會仔細研究這棵樹,希望知道對於這張圖自己的獲勝概率是多少。現請你幫忙求出這個值以驗證聰聰的答案是否正確。
輸入輸出格式
輸入格式:
輸入的第1行包含1個正整數n。后面n-1行,每行3個整數x、y、w,表示x號點和y號點之間有一條邊,上面的數是w。
輸出格式:
以即約分數形式輸出這個概率(即“a/b”的形式,其中a和b必須互質。如果概率為1,輸出“1/1”)。
輸入輸出樣例
說明
【樣例說明】
13組點對分別是(1,1) (2,2) (2,3) (2,5) (3,2) (3,3) (3,4) (3,5) (4,3) (4,4) (5,2) (5,3) (5,5)。
【數據規模】
對於100%的數據,n<=20000。
很明顯,思路就是統計長度為$3$的倍數的路徑的條數,然后除以路徑總和就是答案
先貼一句話題解:先用點分計算出路徑長度,把路徑長度對$3$取模,然后用$sum[1],sum[2],sum[0]$表示模數是$1,2,3$的情況的總數,那么就是$ans+=sum[1]*sum[2]*2+sum[0]*sum[0]$,最后答案就是$ans/(n*n)$
用人話說的話,我們可以先考慮一個點,用$sum[1,2,3]$分別表示從以這一個點為根,往下的長度對$3$取模余數是$1,2,3$的路徑條數,那么所有經過這一個點的路徑有多少條呢?所有長度為$1$和$2$的路徑可以兩兩拼起來成為一條,反着也可以,長度為$3$的路徑可以兩兩拼。所以答案就加上上面那個式子
然后進行點分,不斷遞歸就可以了

1 //minamoto 2 #include<iostream> 3 #include<cstdio> 4 #define ll long long 5 #define inf 0x3f3f3f3f 6 #define getc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++) 7 char buf[1<<21],*p1=buf,*p2=buf; 8 template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;} 9 inline int read(){ 10 #define num ch-'0' 11 char ch;bool flag=0;int res; 12 while(!isdigit(ch=getc())) 13 (ch=='-')&&(flag=true); 14 for(res=num;isdigit(ch=getc());res=res*10+num); 15 (flag)&&(res=-res); 16 #undef num 17 return res; 18 } 19 char sr[1<<21],z[20];int C=-1,Z; 20 inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;} 21 inline void print(int x){ 22 if(C>1<<20)Ot();if(x<0)sr[++C]=45,x=-x; 23 while(z[++Z]=x%10+48,x/=10); 24 while(sr[++C]=z[Z],--Z); 25 } 26 const int N=20005,mod=3; 27 int head[N],Next[N<<1],edge[N<<1],ver[N<<1];ll ans=0; 28 int sz[N],son[N],sum[4],vis[N]; 29 int size,mx,rt,n,tot; 30 inline void add(int u,int v,int e){ 31 ver[++tot]=v,Next[tot]=head[u],head[u]=tot,edge[tot]=e; 32 ver[++tot]=u,Next[tot]=head[v],head[v]=tot,edge[tot]=e; 33 } 34 void getrt(int u,int fa){ 35 sz[u]=1,son[u]=0; 36 for(int i=head[u];i;i=Next[i]){ 37 int v=ver[i]; 38 if(vis[v]||v==fa) continue; 39 getrt(v,u); 40 sz[u]+=sz[v]; 41 cmax(son[u],sz[v]); 42 } 43 cmax(son[u],size-sz[u]); 44 if(son[u]<mx) mx=son[u],rt=u; 45 } 46 void query(int u,int fa,int d){ 47 ++sum[d%mod]; 48 for(int i=head[u];i;i=Next[i]){ 49 int v=ver[i]; 50 if(vis[v]||v==fa) continue; 51 query(v,u,(d+edge[i])%mod); 52 } 53 } 54 ll solve(int rt,int d){ 55 sum[0]=sum[1]=sum[2]=0; 56 query(rt,0,d); 57 ll res=1ll*sum[1]*sum[2]*2+1ll*sum[0]*sum[0]; 58 return res; 59 } 60 void divide(int u){ 61 ans+=solve(u,0); 62 vis[u]=1; 63 for(int i=head[u];i;i=Next[i]){ 64 int v=ver[i]; 65 if(vis[v]) continue; 66 ans-=solve(v,edge[i]); 67 mx=inf,rt=0,size=sz[v]; 68 getrt(v,0); 69 divide(rt); 70 } 71 } 72 inline ll gcd(ll a,ll b){ 73 while(b^=a^=b^=a%=b); 74 return a; 75 } 76 int main(){ 77 n=read(); 78 for(int i=1;i<n;++i){ 79 int u=read(),v=read(),e=read(); 80 add(u,v,e%3); 81 } 82 mx=inf,size=n,ans=0,rt=0; 83 getrt(1,0),divide(rt); 84 ll p=n*n,GCD=gcd(ans,p); 85 print(ans/GCD),sr[++C]='/',print(p/GCD); 86 Ot(); 87 return 0; 88 }
洛谷P3806 【模板】點分治1
題目描述
給定一棵有n個點的樹
詢問樹上距離為k的點對是否存在。
輸入輸出格式
輸入格式:
n,m 接下來n-1條邊a,b,c描述a到b有一條長度為c的路徑
接下來m行每行詢問一個K
輸出格式:
對於每個K每行輸出一個答案,存在輸出“AYE”,否則輸出”NAY”(不包含引號)
輸入輸出樣例
說明
對於30%的數據n<=100
對於60%的數據n<=1000,m<=50
對於100%的數據n<=10000,m<=100,c<=1000,K<=10000000
考慮一下$k$的范圍,干脆預處理出答案然后直接$O(1)$回答詢問吧……
就是把每一個節點向下長度為$d$的路徑有多少條記下來,然后兩兩合並,時間復雜度$O(n^2)$,不知道為毛能過……

//minamoto #include<cstdio> #include<iostream> #define inf 0x3f3f3f3f #define getc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++) char buf[1<<21],*p1=buf,*p2=buf; template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;} inline int read(){ #define num ch-'0' char ch;bool flag=0;int res; while(!isdigit(ch=getc())) (ch=='-')&&(flag=true); for(res=num;isdigit(ch=getc());res=res*10+num); (flag)&&(res=-res); #undef num return res; } const int N=10005; int ans[10000005]; int ver[N<<1],head[N],Next[N<<1],edge[N<<1]; int sz[N],son[N],st[N];bool vis[N]; int n,m,size,mx,rt,tot,top; inline void add(int u,int v,int e){ ver[++tot]=v,Next[tot]=head[u],head[u]=tot,edge[tot]=e; ver[++tot]=u,Next[tot]=head[v],head[v]=tot,edge[tot]=e; } void getrt(int u,int fa){ sz[u]=1,son[u]=0; for(int i=head[u];i;i=Next[i]){ int v=ver[i]; if(vis[v]||v==fa) continue; getrt(v,u); sz[u]+=sz[v],cmax(son[u],sz[v]); } cmax(son[u],size-sz[u]); if(son[u]<mx) mx=son[u],rt=u; } void query(int u,int fa,int d){ st[++top]=d; for(int i=head[u];i;i=Next[i]){ int v=ver[i]; if(vis[v]||v==fa) continue; query(v,u,d+edge[i]); } } void solve(int rt,int d,int f){ top=0; query(rt,0,d); if(f){ for(int i=1;i<top;++i) for(int j=i+1;j<=top;++j) ++ans[st[i]+st[j]]; } else{ for(int i=1;i<top;++i) for(int j=i+1;j<=top;++j) --ans[st[i]+st[j]]; } } void divide(int u){ vis[u]=true; solve(u,0,1); for(int i=head[u];i;i=Next[i]){ int v=ver[i]; if(vis[v]) continue; solve(v,edge[i],0); mx=inf,rt=0,size=sz[v]; getrt(v,0); divide(rt); } } int main(){ n=read(),m=read(); for(int i=1;i<n;++i){ int u=read(),v=read(),e=read(); add(u,v,e); } rt=0,mx=inf,size=n; getrt(1,0),divide(rt); while(m--){ int k=read(); puts(ans[k]?"AYE":"NAY"); } return 0; }
然后就是關於點分的一些題單了,基本我都寫(chao)了題解的
洛谷P2664 樹上游戲(點分治)------------->蒟蒻的題解
[IOI2011]Race (點分治)(洛谷地址)------------->蒟蒻的題解
動態點分治
一般來說,點分治只能處理靜態的問題
然而如果題目要求待修改怎么辦哩?(當然是修改一次做一次點分,多省事)
這個時候就需要動態點分治大顯身手啦
我們可以對樹中的每一個點維護一個數組或者數據結構,然后通過維護它們來完成狀態之間的轉移
然而一條鏈就可以送我們上天……
我們考慮一下,整棵樹的結構是不變的(如果變了那就是LCT的范圍了),被修改的只有點權。那么,我們每一次進行點分時選到的重心也是不變的。
下面給出點分樹的定義:把點分治時每一層的重心之間連邊,這就構成了一顆高度為$logn$的新樹,我們叫它分治樹。
用人話說的話,就是我們每一次點分完一棵樹,會繼續往子樹里點分,那么我們可以把這一次的重心和子樹里的重心連邊。因為點分的遞歸層數最多只有$logn$層,所以這棵樹高度為$logn$
八成還是沒解釋清楚(畢竟我不太對的起語文老師),來畫個圖吧,這是一棵樹,重心是點$1$
我們假設已經處理完了所有經過點$1$的路徑,然后遞歸進子樹繼續點分,那么實際上原樹被拆成了這么兩棵樹,兩個重心分別為$2$和$6$
那么把第一層的重心和第二層的重心給連接起來(用紅色表示)
然后我們繼續進行點分,我們已經把經過點$2$和點$6$的所有路徑都已經處理完了,那么子樹又會繼續拆分
然后因為子樹大小只有$1$,重心就是他們自己,繼續和上一層的重心連邊
然后這一棵點分樹就建好了
貼代碼(事實上只是在原先的板子上加了一句話而已,就是那句 fa[rt]=u )
那么每一次修改,只要在點分樹里不斷往上跳,就能夠維護整棵樹的信息了
void solve(int u){ vis[u]=true;int totsz=size; for(int i=head[u];i;i=Next[i]){ int v=ver[i]; if(vis[v]) continue; rt=0,son[0]=n+1; size=sz[v]>sz[u]?totsz-sz[u]:sz[v]; findrt(v,0),fa[rt]=u,solve(rt); //事實上,我們只需要記錄點分樹上的父親即可(一般情況下) } }
然后基本的介紹就到這里……似乎還是太抽象了……還是來講幾道題目吧
「BZOJ1095」[ZJOI2007] Hide 捉迷藏
題目描述
Jiajia和Wind是一對恩愛的夫妻,並且他們有很多孩子。某天,Jiajia、Wind和孩子們決定在家里玩捉迷藏游戲。他們的家很大且構造很奇特,由N個屋子和N-1條雙向走廊組成,這N-1條走廊的分布使得任意兩個屋子都互相可達。
游戲是這樣進行的,孩子們負責躲藏,Jiajia負責找,而Wind負責操縱這N個屋子的燈。在起初的時候,所有的燈都沒有被打開。每一次,孩子們只會躲藏在沒有開燈的房間中,但是為了增加刺激性,孩子們會要求打開某個房間的電燈或者關閉某個房間的電燈。為了評估某一次游戲的復雜性,Jiajia希望知道可能的最遠的兩個孩子的距離(即最遠的兩個關燈房間的距離)。
我們將以如下形式定義每一種操作:
- C(hange) i 改變第i個房間的照明狀態,若原來打開,則關閉;若原來關閉,則打開。
- G(ame) 開始一次游戲,查詢最遠的兩個關燈房間的距離。
輸入輸出格式
輸入格式:
第一行包含一個整數N,表示房間的個數,房間將被編號為1,2,3…N的整數。
接下來N-1行每行兩個整數a, b,表示房間a與房間b之間有一條走廊相連。
接下來一行包含一個整數Q,表示操作次數。接着Q行,每行一個操作,如上文所示。
輸出格式:
對於每一個操作Game,輸出一個非負整數到hide.out,表示最遠的兩個關燈房間的距離。若只有一個房間是關着燈的,輸出0;若所有房間的燈都開着,輸出-1。
輸入輸出樣例
說明
對於20%的數據, N ≤50, M ≤100;
對於60%的數據, N ≤3000, M ≤10000; 對於100%的數據, N ≤100000, M ≤500000。
動態點分治的板子題
(然而我該說我當初做這道題是抄了島娘和hzwer的括號序列么……因為看着覺得代碼比較好抄……所以根本沒打過動態點分的板子……)
我就直接說一下思路(然后隨便放個別的大佬的代碼好了)
我們考慮一下,在每一個點需要維護這一個點在點分樹中往下延伸到黑點的所有長度,然后每一次都把最長的和次長的給連接起來
這個可以每一個節點開一個大根堆
然而如果兩條鏈是來自同一棵子樹的就GG了……
那么可以再開一個堆,維護這一個點的所有兒子的堆頂,然后每一次取出的最長和次長可以保證不是在同一棵子樹里
然后再開一個堆維護一下每一個節點的答案……
考慮修改怎么操作?每一次在第一個堆里進行修改,維護第二個堆和第三個堆就可以了
分治樹深度$logn$,堆操作時間復雜度是$logn$,總時間復雜度是$O(nlog^2n)$
然后放一下LadyLex大佬的代碼吧(才不是因為我懶得打呢)

1 #include <cstdio> 2 #include <cstring> 3 #include <ctime> 4 #include <set> 5 #include <queue> 6 using namespace std; 7 #define N 100010 8 #define inf 0x3fffffff 9 #define Vt Vater[rt] 10 int n,e,adj[N]; 11 struct edge{int zhong,next;}s[N<<1]; 12 inline void add(int qi,int zhong) 13 {s[++e].zhong=zhong;s[e].next=adj[qi];adj[qi]=e;} 14 int Vater[N],size[N],root,totsize,maxs[N]; 15 bool state[N],vis[N]; 16 #define max(a,b) ((a)>(b)?(a):(b)) 17 #define min(a,b) ((a)<(b)?(a):(b)) 18 struct heap 19 { 20 priority_queue<int>q1,q2; 21 inline void push(int x){q1.push(x);} 22 inline void erase(int x){q2.push(x);} 23 inline int top() 24 { 25 while(q2.size()&&q1.top()==q2.top())q1.pop(),q2.pop(); 26 return q1.top(); 27 } 28 inline void pop() 29 { 30 while(q2.size()&&q1.top()==q2.top())q1.pop(),q2.pop(); 31 q1.pop(); 32 } 33 inline int top2() 34 { 35 int val=top();pop(); 36 int ret=top();push(val); 37 return ret; 38 } 39 inline int size() 40 { 41 return q1.size()-q2.size(); 42 } 43 }h1[N],h2[N],h3; 44 inline void dfs1(int rt,int fa) 45 { 46 size[rt]=1,maxs[rt]=0; 47 for(int i=adj[rt];i;i=s[i].next) 48 if(s[i].zhong!=fa&&!vis[s[i].zhong]) 49 dfs1(s[i].zhong,rt),size[rt]+=size[s[i].zhong], 50 maxs[rt]=max(maxs[rt],size[s[i].zhong]); 51 maxs[rt]=max(maxs[rt],totsize-maxs[rt]); 52 if(maxs[rt]<maxs[root])root=rt; 53 } 54 int f[N][18],bin[25],tp,deep[N]; 55 inline void pre(int rt,int fa) 56 { 57 f[rt][0]=fa;deep[rt]=deep[fa]+1; 58 for(int i=1;bin[i]+1<=deep[rt];++i)f[rt][i]=f[f[rt][i-1]][i-1]; 59 for(int i=adj[rt];i;i=s[i].next) 60 if(s[i].zhong!=fa)pre(s[i].zhong,rt); 61 } 62 inline int LCA(int a,int b) 63 { 64 if(deep[a]<deep[b])a^=b,b^=a,a^=b; 65 int i,cha=deep[a]-deep[b]; 66 for(i=tp;~i;--i)if(cha&bin[i])a=f[a][i]; 67 if(a==b)return a; 68 for(i=tp;~i;--i) 69 if(f[a][i]!=f[b][i])a=f[a][i],b=f[b][i]; 70 return f[a][0]; 71 } 72 inline int dis(int a,int b) 73 {return deep[a]+deep[b]-(deep[LCA(a,b)]<<1);} 74 inline void dfs3(int rt,int fa,int Vatty) 75 { 76 h1[root].push(dis(rt,Vatty)); 77 for(int i=adj[rt];i;i=s[i].next) 78 if(!vis[s[i].zhong]&&s[i].zhong!=fa) 79 dfs3(s[i].zhong,rt,Vatty); 80 } 81 inline void dfs2(int rt,int fa) 82 { 83 Vt=fa,vis[rt]=1,h2[rt].push(0); 84 int siz=totsize; 85 for(int i=adj[rt];i;i=s[i].next) 86 if(!vis[s[i].zhong]) 87 { 88 if(size[s[i].zhong]>size[rt]) 89 totsize=siz-size[rt]; 90 else 91 totsize=size[s[i].zhong]; 92 root=0,dfs1(s[i].zhong,0),dfs3(root,0,rt); 93 h2[rt].push(h1[root].top()),dfs2(root,rt); 94 } 95 if(h2[rt].size()>1)h3.push(h2[rt].top()+h2[rt].top2()); 96 } 97 inline void turnoff(int who) 98 { 99 int val,tmp; 100 if(h2[who].size()>1)h3.erase(h2[who].top()+h2[who].top2()); 101 h2[who].push(0); 102 if(h2[who].size()>1)h3.push(h2[who].top()+h2[who].top2()); 103 //queue empty() 后依然有數 104 for(int rt=who;Vt;rt=Vt) 105 { 106 if(h2[Vt].size()>1)h3.erase(h2[Vt].top()+h2[Vt].top2()); 107 if(h1[rt].size())h2[Vt].erase(h1[rt].top()); 108 h1[rt].push(dis(who,Vt)); 109 h2[Vt].push(h1[rt].top()); 110 if(h2[Vt].size()>1)h3.push(h2[Vt].top()+h2[Vt].top2()); 111 } 112 } 113 inline void turnon(int who) 114 { 115 int val,tmp; 116 if(h2[who].size()>1)h3.erase(h2[who].top()+h2[who].top2()); 117 h2[who].erase(0); 118 if(h2[who].size()>1)h3.push(h2[who].top()+h2[who].top2()); 119 //queue empty()后依然有數 120 for(int rt=who;Vt;rt=Vt) 121 { 122 if(h2[Vt].size()>1)h3.erase(h2[Vt].top()+h2[Vt].top2()); 123 h2[Vt].erase(h1[rt].top()); 124 h1[rt].erase(dis(who,Vt)); 125 if(h1[rt].size())h2[Vt].push(h1[rt].top()); 126 if(h2[Vt].size()>1)h3.push(h2[Vt].top()+h2[Vt].top2()); 127 } 128 } 129 char B[1<<15],X=0,*S=B,*T=B; 130 #define getc ( S==T&&( T=(S=B)+fread(B,1,1<<15,stdin),S==T )?0:*S++ ) 131 inline int read() 132 { 133 int x=0;while(X<'0'||X>'9')X=getc; 134 while(X>='0'&&X<='9')x=10*x+(X^48),X=getc; 135 return x; 136 } 137 inline void readc(){X=getc;while(X<'A'||X>'Z')X=getc;} 138 int main() 139 { 140 // freopen("hide1.in","r",stdin); 141 // freopen("hide.out","w",stdout); 142 n=read(); 143 register int i,j,q,a,b,cnt=n; 144 for(bin[0]=i=1;i<=20;++i)bin[i]=bin[i-1]<<1; 145 while(bin[tp+1]<=n)++tp; 146 for(i=1;i<n;++i) 147 a=read(),b=read(),add(a,b),add(b,a); 148 pre(1,0); 149 maxs[0]=inf,root=0,totsize=n,dfs1(1,0),dfs2(root,0); 150 q=read(); 151 while(q--) 152 { 153 readc(); 154 if(X=='C') 155 { 156 i=read(); 157 if(state[i])++cnt,turnoff(i); 158 else --cnt,turnon(i); 159 state[i]^=1; 160 } 161 else 162 { 163 if(cnt<2)printf("%d\n",cnt-1); 164 else printf("%d\n",h3.top()); 165 } 166 } 167 }
BZOJ 3924: [Zjoi2015]幻想鄉戰略游戲(動態點分治)
題目描述
傲嬌少女幽香正在玩一個非常有趣的戰略類游戲,本來這個游戲的地圖其實還不算太大,幽香還能管得過來,但是不知道為什么現在的網游廠商把游戲的地圖越做越大,以至於幽香一眼根本看不過來,更別說和別人打仗了。
在打仗之前,幽香現在面臨一個非常基本的管理問題需要解決。 整個地圖是一個樹結構,一共有n塊空地,這些空地被n-1條帶權邊連接起來,使得每兩個點之間有一條唯一的路徑將它們連接起來。
在游戲中,幽香可能在空地上增加或者減少一些軍隊。同時,幽香可以在一個空地上放置一個補給站。 如果補給站在點u上,並且空地v上有dv個單位的軍隊,那么幽香每天就要花費dv*dist(u,v)的金錢來補給這些軍隊。
由於幽香需要補給所有的軍隊,因此幽香總共就要花費為Sigma(Dv*dist(u,v),其中1<=V<=N)的代價。其中dist(u,v)表示u個v在樹上的距離(唯一路徑的權和)。
因為游戲的規定,幽香只能選擇一個空地作為補給站。在游戲的過程中,幽香可能會在某些空地上制造一些軍隊,也可能會減少某些空地上的軍隊,進行了這樣的操作以后,出於經濟上的考慮,幽香往往可以移動他的補給站從而省一些錢。
但是由於這個游戲的地圖是在太大了,幽香無法輕易的進行最優的安排,你能幫幫她嗎? 你可以假定一開始所有空地上都沒有軍隊。
輸入輸出格式
輸入格式:
第一行兩個數n和Q分別表示樹的點數和幽香操作的個數,其中點從1到n標號。 接下來n-1行,每行三個正整數a,b,c,表示a和b之間有一條邊權為c的邊。 接下來Q行,每行兩個數u,e,表示幽香在點u上放了e單位個軍隊(如果e<0,就相當於是幽香在u上減少了|e|單位個軍隊,說白了就是du←du+e)。數據保證任何時刻每個點上的軍隊數量都是非負的。
輸出格式:
對於幽香的每個操作,輸出操作完成以后,每天的最小花費,也即如果幽香選擇最優的補給點進行補給時的花費。
輸入輸出樣例
10 5 1 2 1 2 3 1 2 4 1 1 5 1 2 6 1 2 7 1 5 8 1 7 9 1 1 10 1 3 1 2 1 8 1 3 1 4 1
0 1 4 5 6
說明
對於所有數據,1<=c<=1000, 0<=|e|<=1000, n<=10^5, Q<=10^5 非常神奇的是,對於所有數據,這棵樹上的點的度數都不超過20,且N,Q>=1
我們具體來分析一下這道題目我們要維護什么
題目要求使$\sum d_v*dis(u,v)$最小,其中$d_v$為$v$點的點權,$dis(u,v)$為原樹中$u,v$兩點的距離。題目要求就是求帶權重心。假設我們當前已經選定了點$v$為答案,那么考慮它的一個子節點$w$,如果把補給站從點$v$轉移到$w$,那么$w$的所有子樹內的點到補給站的距離少了$dis(v,w)$,而其他所有點到補給站的距離多了$dis(v,w)$。我們假設$sum_v[v]$為$v$的子樹內的點權和,那么答案總共的變化量是$$dis(u,v)*(sum_v[v]-sum_v[w]-sum_v[w])$$
不難發現,當$sum_v[w]*2>sum_v[v]$的時候,答案的變化量是小於零的,也就是說代價減小,可以變得更優。而且,滿足這樣條件的點$w$最多只有一個
那么我們可以每一次選定一個點,然后看看往他的哪個子樹走更優,如果沒有說明他自己就已經是最優的了。這樣不斷下去肯定能找到答案。
但是由於原圖可能是一條鏈,要怎么做才能保證復雜度呢?我們選擇在點分樹上走,每一次都跳到它下一層的重心,這樣可以保證層數最多只有$O(log n)$
然后考慮如何維護答案。不難發現,對於點分樹上一個點$v$,它的子樹就是在點分治時它被選為重心時的那棵樹。考慮點分樹上的一對父子$u,v$,我們設$sum_a[v]$表示$v$的子樹內的所有點到他的代價之和,$sum_b[v]$為$v$的子樹內的所有點到$v$點父親(也就是點$u$)的距離之和,$sum_v[v]$還是表示子樹的點權之和。那么我們設答案已經選定為點$w$,並且已經把$v$這一整棵子樹上所有點到點$w$的距離之和計算完畢,那么考慮要加上$v$在點分樹上的父親$u$,要計算的答案只有$u$的不包含$v$的子樹,答案是$sum_a[v]+sum_a[u]-sum_b[v]+sum_v[u]*dis(u,v)$。於是只要在點分樹上不斷找父親並合並,就可以知道答案了
然后我們只要能在修改時維護好這三個數組就可以了
至於修改時如何維護呢?我們修改一個點之后,然后不斷在點分樹上往父節點跳,每一次只會對它的祖先節點的這些值產生影響,一個一個修改即可
ps:還有個小細節,因為我們判斷是否更優要在原樹上走,然后計算答案要在點分樹里走,所以對於每一個節點不僅要記錄它的父親,還要記錄它往下能走哪幾條邊,在原樹的邊判斷是否更優,然后往點分樹上走來保證計算答案的復雜度
然后又新學會了一招,用$RMQ O(1)$查詢$LCA$(只會倍增和樹剖的我瑟瑟發抖),總時間復雜度$O(nlog^2n)$

1 //minamoto 2 #include<cstdio> 3 #include<iostream> 4 #include<cstring> 5 #define ll long long 6 #define N 100005 7 #define inf 0x3f3f3f3f 8 #define rint register int 9 using namespace std; 10 #define getc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++) 11 char buf[1<<21],*p1=buf,*p2=buf; 12 template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;} 13 inline int read(){ 14 #define num ch-'0' 15 char ch;bool flag=0;int res; 16 while(!isdigit(ch=getc())) 17 (ch=='-')&&(flag=true); 18 for(res=num;isdigit(ch=getc());res=res*10+num); 19 (flag)&&(res=-res); 20 #undef num 21 return res; 22 } 23 char sr[1<<21],z[20];int C=-1,Z; 24 inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;} 25 inline void print(ll x){ 26 if(C>1<<20)Ot();if(x<0)sr[++C]=45,x=-x; 27 while(z[++Z]=x%10+48,x/=10); 28 while(sr[++C]=z[Z],--Z);sr[++C]='\n'; 29 } 30 struct G{ 31 int head[N],Next[N<<1],edge[N<<1],ver[N<<1],tot; 32 G(){tot=0;memset(head,0,sizeof(head));} 33 inline void add(int u,int v,int e){ 34 ver[++tot]=v,Next[tot]=head[u],head[u]=tot,edge[tot]=e; 35 } 36 }T1,T2; 37 int n,q,st[N<<1][18],logn[N<<1],bin[25],tp; 38 ll sum,ans,d[N],dis1[N],dis2[N],sumv[N]; 39 int dfn[N],num; 40 void dfs1(int u,int fa){ 41 st[dfn[u]=++num][0]=d[u]; 42 for(int i=T1.head[u];i;i=T1.Next[i]){ 43 int v=T1.ver[i]; 44 if(v==fa) continue; 45 d[v]=d[u]+T1.edge[i],dfs1(v,u),st[++num][0]=d[u]; 46 } 47 } 48 inline ll LCA(int a,int b){ 49 if(dfn[a]>dfn[b]) a^=b^=a^=b; 50 int k=logn[dfn[b]-dfn[a]+1]; 51 return min(st[dfn[a]][k],st[dfn[b]-bin[k]+1][k])<<1; 52 } 53 inline ll dis(int a,int b){return d[a]+d[b]-LCA(a,b);} 54 int sz[N],son[N],size,rt,fa[N];bool vis[N]; 55 void dfs2(int u,int fa){ 56 sz[u]=1,son[u]=0; 57 for(int i=T1.head[u];i;i=T1.Next[i]){ 58 int v=T1.ver[i]; 59 if(vis[v]||v==fa) continue; 60 dfs2(v,u),sz[u]+=sz[v],cmax(son[u],sz[v]); 61 } 62 cmax(son[u],size-sz[u]); 63 if(son[u]<son[rt]) rt=u; 64 } 65 void dfs3(int u){ 66 vis[u]=true; 67 for(int i=T1.head[u];i;i=T1.Next[i]){ 68 int v=T1.ver[i]; 69 if(vis[v]) continue; 70 rt=0,size=sz[v],son[0]=n+1; 71 dfs2(v,0),T2.add(u,rt,v),fa[rt]=u,dfs3(rt); 72 } 73 } 74 inline void update(int u,int val){ 75 sumv[u]+=val; 76 for(int p=u;fa[p];p=fa[p]){ 77 ll dist=dis(fa[p],u)*val; 78 dis1[fa[p]]+=dist; 79 dis2[p]+=dist; 80 sumv[fa[p]]+=val; 81 } 82 } 83 inline ll calc(int u){ 84 ll ans=dis1[u]; 85 for(int p=u;fa[p];p=fa[p]){ 86 ll dist=dis(fa[p],u); 87 ans+=dis1[fa[p]]-dis2[p]; 88 ans+=dist*(sumv[fa[p]]-sumv[p]); 89 } 90 return ans; 91 } 92 ll query(int u){ 93 ll ans=calc(u); 94 for(int i=T2.head[u];i;i=T2.Next[i]){ 95 ll tmp=calc(T2.edge[i]); 96 if(tmp<ans) return query(T2.ver[i]); 97 } 98 return ans; 99 } 100 void init(){ 101 n=read(),q=read(); 102 bin[0]=1,logn[0]=-1; 103 for(rint i=1;i<=20;++i) bin[i]=bin[i-1]<<1; 104 while(bin[tp+1]<=(n<<1)) ++tp; 105 for(rint i=1;i<=(n<<1);++i) logn[i]=logn[i>>1]+1; 106 for(rint i=1;i<n;++i){ 107 rint u=read(),v=read(),e=read(); 108 T1.add(u,v,e),T1.add(v,u,e); 109 } 110 dfs1(1,0),rt=0,son[0]=n+1,size=n,dfs2(1,0); 111 for(rint j=1;j<=tp;++j) 112 for(rint i=1;i+bin[j]-1<=(n<<1);++i) 113 st[i][j]=min(st[i][j-1],st[i+bin[j-1]][j-1]); 114 } 115 int main(){ 116 init(); 117 int LastOrder=rt;dfs3(rt); 118 while(q--){ 119 int x=read(),y=read();update(x,y); 120 print(query(LastOrder)); 121 } 122 Ot(); 123 return 0; 124 }
BZOJ4012 [HNOI2015]開店 (動態點分治)
Description
風見幽香有一個好朋友叫八雲紫,她們經常一起看星星看月亮從詩詞歌賦談到
Input
第一行三個用空格分開的數 n、Q和A,表示樹的大小、開店的方案個數和妖
Output
對於每個方案,輸出一行表示方便值。
Sample Input
0 0 7 2 1 4 7 7 7 9
1 2 270
2 3 217
1 4 326
2 5 361
4 6 116
3 7 38
1 8 800
6 9 210
7 10 278
8 9 8
2 8 0
9 3 1
8 0 8
4 2 7
9 7 3
4 7 0
2 2 7
3 2 1
2 3 4
Sample Output
957
7161
9466
3232
5223
1879
1669
1282
0
HINT
滿足 n<=150000,Q<=200000。對於所有數據,滿足 A<=10^9
我們考慮對於每一個點分樹上的點維護什么。我們記錄三個值,$sz_0$表示子樹內的點數之和,$sz_1$表示子樹內所有點到其的距離之和,$sz_2$表示子樹內所有點到其父親的距離之和。那么考慮我們在跳點分樹的時候要如何維護答案呢?很明顯$sz_1[u]+\sum sz_1[fa]-sz_2[p]+(sz_0[fa]-sz_0[p])*dist(fa,u)$就是在點分樹上與$u$的$LCA$是$fa$的所有點的距離之和,其中$fa$為$p$的父親,$p$為從$u$不斷跳父親直到根,那么只要不斷枚舉$fa$,並不斷往上跳並維護答案就可以了。
然而上面只是為了方便理解,因為具體的計算不是這樣的。我們對於$u$點內部的貢獻可以直接計算,然后考慮往上跳。在上述的式子中如果一個點$p$有$fa$,那么答案就要減去$sz_0[p]*dist(fa,u)+sz_2[p]$,如果一個點不是$u$那么答案就要加上$sz_0[p]+dist(p,u)$,然后每一個點都要加上自己的$sz_1$。按這個規律在跳點分樹的時候不斷加就好了
然后考慮怎么在$[l,r]$之內,很明顯可以搞一個差分,把每一個節點在點分樹上的子樹內的所有年齡的距離之和加起來,再做前綴和,那么在年齡$[l,r]$的人數就是$[1,r]-[1,l-1]$(因為實際上存儲時不可能按年齡,所以可以在每一個點開一個vector然后排序一下即可)

1 //minamoto 2 #include<cstdio> 3 #include<iostream> 4 #include<cstring> 5 #include<vector> 6 #include<algorithm> 7 #define ll long long 8 #define N 150005 9 using namespace std; 10 #define getc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++) 11 char buf[1<<21],*p1=buf,*p2=buf; 12 template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;} 13 inline int read(){ 14 #define num ch-'0' 15 char ch;bool flag=0;int res; 16 while(!isdigit(ch=getc())) 17 (ch=='-')&&(flag=true); 18 for(res=num;isdigit(ch=getc());res=res*10+num); 19 (flag)&&(res=-res); 20 #undef num 21 return res; 22 } 23 char sr[1<<21],z[20];int C=-1,Z; 24 inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;} 25 inline void print(ll x){ 26 if(C>1<<20)Ot();if(x<0)sr[++C]=45,x=-x; 27 while(z[++Z]=x%10+48,x/=10); 28 while(sr[++C]=z[Z],--Z);sr[++C]='\n'; 29 } 30 int head[N],Next[N<<1],ver[N<<1],edge[N<<1]; 31 int n,tot,val[N],q,maxn; 32 int st[N<<1][19],d[N],dfn[N],num,bin[25],tp,logn[N<<1]; 33 inline void add(int u,int v,int e){ 34 ver[++tot]=v,Next[tot]=head[u],head[u]=tot,edge[tot]=e; 35 ver[++tot]=u,Next[tot]=head[v],head[v]=tot,edge[tot]=e; 36 } 37 inline void ST(){ 38 for(int j=1;j<=tp;++j) 39 for(int i=1;i+bin[j]-1<=(n<<1);++i) 40 st[i][j]=min(st[i][j-1],st[i+bin[j-1]][j-1]); 41 } 42 void dfs1(int u,int fa){ 43 st[dfn[u]=++num][0]=d[u]; 44 for(int i=head[u];i;i=Next[i]){ 45 int v=ver[i]; 46 if(v==fa) continue; 47 d[v]=d[u]+edge[i],dfs1(v,u),st[++num][0]=d[u]; 48 } 49 } 50 int fa[N],sz[N],son[N],size,rt;bool vis[N]; 51 void dfs2(int u,int fa){ 52 sz[u]=1,son[u]=0; 53 for(int i=head[u];i;i=Next[i]){ 54 int v=ver[i]; 55 if(vis[v]||v==fa) continue; 56 dfs2(v,u),sz[u]+=sz[v],cmax(son[u],sz[v]); 57 } 58 cmax(son[u],size-sz[u]); 59 if(son[u]<son[rt]) rt=u; 60 } 61 inline ll dis(int a,int b){ 62 if(dfn[a]>dfn[b]) a^=b^=a^=b; 63 int k=logn[dfn[b]-dfn[a]+1]; 64 return d[a]+d[b]-(min(st[dfn[a]][k],st[dfn[b]-bin[k]+1][k])<<1); 65 } 66 struct node{ 67 int val;ll sz[3]; 68 node(int a=0,ll b=0,ll c=0,ll d=0){val=a,sz[0]=b,sz[1]=c,sz[2]=d;} 69 inline bool operator <(const node &b)const 70 {return val<b.val;} 71 }; 72 vector<node> sta[N]; 73 void dfs3(int u,int f,int rt){ 74 sta[rt].push_back(node(val[u],1,dis(u,rt),fa[rt]?dis(u,fa[rt]):0)); 75 for(int i=head[u];i;i=Next[i]){ 76 int v=ver[i]; 77 if(v==f||vis[v]) continue; 78 dfs3(v,u,rt); 79 } 80 } 81 void dfs4(int u){ 82 vis[u]=true; 83 dfs3(u,0,u);sta[u].push_back(node(-1,0,0,0)); 84 sort(sta[u].begin(),sta[u].end()); 85 for(int i=0,j=sta[u].size();i<j-1;++i) 86 sta[u][i+1].sz[0]+=sta[u][i].sz[0], 87 sta[u][i+1].sz[1]+=sta[u][i].sz[1], 88 sta[u][i+1].sz[2]+=sta[u][i].sz[2]; 89 for(int i=head[u];i;i=Next[i]){ 90 int v=ver[i]; 91 if(vis[v]) continue; 92 rt=0,size=sz[v]; 93 dfs2(v,0),fa[rt]=u,dfs4(rt); 94 } 95 } 96 inline node query(int id,int l,int r){ 97 if(id==0) return node(); 98 vector<node>::iterator it1=upper_bound(sta[id].begin(),sta[id].end(),node(r,0,0,0));--it1; 99 vector<node>::iterator it2=upper_bound(sta[id].begin(),sta[id].end(),node(l-1,0,0,0));--it2; 100 return node(0,it1->sz[0]-it2->sz[0],it1->sz[1]-it2->sz[1],it1->sz[2]-it2->sz[2]); 101 } 102 inline ll calc(int u,int l,int r){ 103 ll res=0; 104 for(int p=u;p;p=fa[p]){ 105 node a=query(p,l,r); 106 res+=a.sz[1]; 107 if(p!=u) res+=a.sz[0]*dis(p,u); 108 if(fa[p]) res-=a.sz[2]+a.sz[0]*dis(fa[p],u); 109 } 110 return res; 111 } 112 int main(){ 113 ll ans=0; 114 n=read(),q=read(),maxn=read(); 115 bin[0]=1,logn[0]=-1; 116 for(int i=1;i<=20;++i) bin[i]=bin[i-1]<<1; 117 while(bin[tp+1]<=(n<<1)) ++tp; 118 for(int i=1;i<=(n<<1);++i) logn[i]=logn[i>>1]+1; 119 for(int i=1;i<=n;++i) val[i]=read(); 120 for(int i=1;i<n;++i){ 121 int u=read(),v=read(),e=read(); 122 add(u,v,e); 123 } 124 dfs1(1,0),ST(); 125 rt=0,son[0]=n+1,size=n,dfs2(1,0); 126 dfs4(rt); 127 while(q--){ 128 int a=read(),b=read(),c=read(); 129 b=(b+ans)%maxn,c=(c+ans)%maxn; 130 if(b>c) b^=c^=b^=c; 131 print(ans=calc(a,b,c)); 132 } 133 Ot(); 134 return 0; 135 }
emm……最后再來兩個大boss吧(我已經想(抄)到了一種很棒的解法可惜這里寫不下)
說真的抄代碼其實很累的
洛谷P3676 小清新數據結構題--------->蒟蒻的題解
bzoj3435 [Wc2014]紫荊花之戀(權限)(非權限)---------------->蒟蒻的題解
那么就到這里吧……感謝觀看……累死我了……