點分治&動態點分治小結


(寫篇博客證明自己還活着×2)

轉載請注明原文地址:http://www.cnblogs.com/LadyLex/p/8006488.html 

有的時候,我們會發現這樣一類題:它長得很像一個$O(n)$樹規,

但是卻很難用單獨的數組維護對應的信息,這樣我們就有了淀粉質點分治。

通過直接統計($O(nlogn)$)或者加上數據結構(比如樹狀數組,堆,線段樹等等)維護信息($O(nlog^{2}n)$),

我們可以統計之前不好統計的東西

這篇博客將會介紹點分治以及動態點分治的簡單應用,希望閱讀本文的你能有所收獲,那就再好不過了。

一.樹規到點分治

我們先來看一道簡單的題目……

2152: 聰聰可可

Time Limit: 3 Sec  Memory Limit: 259 MB

Description

聰聰和可可是兄弟倆,他們倆經常為了一些瑣事打起來,例如家中只剩下最后一根冰棍而兩人都想吃、兩個人都想玩兒電腦(可是他們家只有一台電腦)……遇到這種問題,一般情況下石頭剪刀布就好了,可是他們已經玩兒膩了這種低智商的游戲。他們的爸爸快被他們的爭吵煩死了,所以他發明了一個新游戲:由爸爸在紙上畫n個“點”,並用n-1條“邊”把這n個“點”恰好連通(其實這就是一棵樹)。並且每條“邊”上都有一個數。接下來由聰聰和可可分別隨即選一個點(當然他們選點時是看不到這棵樹的),如果兩個點之間所有邊上數的和加起來恰好是3的倍數,則判聰聰贏,否則可可贏。聰聰非常愛思考問題,在每次游戲后都會仔細研究這棵樹,希望知道對於這張圖自己的獲勝概率是多少。現請你幫忙求出這個值以驗證聰聰的答案是否正確。

Input

輸入的第1行包含1個正整數n。后面n-1行,每行3個整數x、y、w,表示x號點和y號點之間有一條邊,上面的數是w。

Output

以即約分數形式輸出這個概率(即“a/b”的形式,其中a和b必須互質。如果概率為1,輸出“1/1”)。

Sample Input

5
1 2 1
1 3 2
1 4 1
2 5 3

Sample Output

13/25
【樣例說明】
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的倍數%3=0(廢話……),因此我們可以設$f[i][0/1/2]$為以i為根的子樹,向下伸出的路徑中長度%3=0/1/2的路徑數。
這樣我們在每一個點簡單的合並一下(注意去掉同一子樹的貢獻)即可。
這樣的做法顯然是$O(n)$的對吧……
現在讓我們考慮一個蠢一點的做法:
我們依然考慮每個點作為路徑頂點時符合條件的點的個數,那么我們可以考慮它的每個子樹,
數一下里面每個路徑的條數,然后合起來對吧。
對於這個節點子樹中的節點,我們是化作子問題遞歸處理的。
那么我們可以把樹換個形狀……換一個順序來遞歸處理
由於重心有個性質:到其他點的距離和最小,
那我們可以暴力數一下每種長度0/1/2的路徑有多少,再暴力加上,然后對於被這個重心分割出的幾個子樹分治遞歸解決……
這樣找重心的復雜度呢?我們最多找$log$次重心就會只剩下一個點,因此復雜度為$O(nlogn)$. (因為每次找到一個重心,新的子樹最多是原來子樹大小的一半)
那么介紹一下我們必須的操作:
首先是初始化以及數組定義。我的習慣是這樣的:
1 int size[N],maxs[N],totsize,root;
2 bool vis[N];
3 inline void intn()
4 {
5     maxs[0]=inf,root=0,dfs1(1,0),solve(root);
6 }
其中$size[i]$表示目前以i點為根的子樹size大小 $maxs[i]$為i點size最大的兒子的size(這個“兒子”可以是i點在有根樹中的父親)
$totsize$為當前聯通塊大小 $root$為最終的重心 $vis[i]$表示i點是否已經被選取作為重心。vis保證我們可以“截取”出准確的對應聯通塊 然后是找重心的部分:
 1 inline void dfs1(int rt,int fa)
 2 {
 3     size[rt]=1,maxsize[rt]=0;
 4     for(int i=adj[rt];i;i=s[i].next)
 5         if(s[i].zhong!=fa&&!mark[s[i].zhong])
 6         {
 7             dfs1(s[i].zhong,rt),
 8             size[rt]+=size[s[i].zhong],
 9             maxsize[rt]=max(maxsize[rt],size[s[i].zhong]);
10         }
11     maxsize[rt]=max(maxsize[rt],totsize-maxsize[rt]);
12     if(maxsize[rt]<maxsize[root])root=rt;
13 }
這其實類似一個樹規的$O(n)$過程……這樣找到最后$root$就是重心 然后是solve過程:
 1 inline void solve(int rt)
 2 {
 3     vis[rt]=1;
 4   /*
 5     deal with ans
 6   */
 7     for(int i=adj[rt];i;i=s[i].next)
 8         if(!vis[s[i].zhong])
 9             totsize=size[s[i].zhong],root=0,
10             dfs1(s[i].zhong,0),solve(root);
11 }
我們在每一次確定的重心上進行計算,calc函數的內容隨題目而變,但是大概是長這樣的…… 本題的完整代碼:
 1 #include <cstdio>
 2 #include <cstring>
 3 #define N 20010
 4 #define inf 0x7fffffff
 5 #define max(a,b) ((a)>(b)?(a):(b))
 6 int n,e,adj[N],root,totsize,size[N],maxsize[N];
 7 struct edge{int zhong,val,next;}s[N<<1];
 8 inline void add(int qi,int zhong,int val)
 9     {s[++e].zhong=zhong;s[e].next=adj[qi];adj[qi]=e;s[e].val=val;}
10 bool mark[N];
11 inline void dfs1(int rt,int fa)
12 {
13     size[rt]=1,maxsize[rt]=0;
14     for(int i=adj[rt];i;i=s[i].next)
15         if(!mark[s[i].zhong]&&s[i].zhong!=fa)
16             dfs1(s[i].zhong,rt),size[rt]+=size[s[i].zhong],
17             maxsize[rt]=max(maxsize[rt],size[s[i].zhong]);
18     maxsize[rt]=max(maxsize[rt],totsize-size[rt]);
19     if(maxsize[rt]<maxsize[root])root=rt;
20 }
21 int cnt[3],dist[N],ans;
22 inline void dfs2(int rt,int fa)
23 {
24     ++cnt[dist[rt]];
25     for(int i=adj[rt];i;i=s[i].next)
26         if(!mark[s[i].zhong]&&s[i].zhong!=fa)
27             dist[s[i].zhong]=(dist[rt]+s[i].val)%3,
28             dfs2(s[i].zhong,rt);
29 }
30 inline int calc(int rt,int stval)
31 {
32     dist[rt]=stval,cnt[0]=cnt[1]=cnt[2]=0,dfs2(rt,0);
33     return cnt[0]*cnt[0]+2*cnt[1]*cnt[2];
34 }
35 inline void solve(int rt)
36 {
37     mark[rt]=1,ans+=calc(rt,0);
38     for(int i=adj[rt];i;i=s[i].next)
39         if(!mark[s[i].zhong])
40             ans-=calc(s[i].zhong,s[i].val),
41             totsize=size[s[i].zhong],root=0,
42             dfs1(s[i].zhong,0),solve(root);
43 }
44 inline int gcd(int a,int b){return b==0?a:gcd(b,a%b);}
45 int main()
46 {
47     scanf("%d",&n);
48     register int i,a,b,c;
49     for(i=1;i<n;++i)
50         scanf("%d%d%d",&a,&b,&c),add(a,b,c%3),add(b,a,c%3);
51     totsize=n,root=0,maxsize[0]=inf;
52     dfs1(1,0),solve(root);
53     int di=n*n,d=gcd(di,ans);
54     printf("%d/%d\n",ans/d,di/d);
55 }
BZOJ2152
現在這個思路看起來比較蠢對吧……但是如果換一道題,原本的樹規做法就行不通了。

2599: [IOI2011]Race 

Time Limit: 70 Sec  Memory Limit: 128 MB
Description給一棵樹,每條邊有權.求一條簡單路徑,權值和等於K,且邊的數量最小.N <= 200000, K <= 1000000
Input第一行 兩個整數 n, k第二..n行 每行三個整數 表示一條無向邊的兩端和權值 (注意點的編號從0開始)
Output一個整數 表示最小邊數量 如果不存在這樣的路徑 輸出-1
Sample Input
4 3
0 1 1
1 2 2
1 3 4
Sample Output
2
看到這道題,原本樹規的做法顯然行不通了:
如果我們定義$f[i][j]$為以i為根的子樹中長度為j的路徑最小邊數,空間和時間復雜度都會爆炸。
這個時候我們再想想剛才點分治的做法:
我們僅用一個數組$f[j]$表示到每個重心之后子樹中長度為j的路徑最小邊數,
然后到每個重心之后通過dfs遍歷去更新對應的f值,然后再用f數組之間相加==k的元素之和更新答案。
這樣,我們就用一個$log$的代價,解決了樹規解決很困難的問題。時間復雜度$O(nlogn)$.
代碼: 
 1 #include <cstdio>
 2 #include <cstring>
 3 using namespace std;
 4 #define N 200010
 5 #define K 1000010
 6 #define LL long long
 7 #define max(a,b) ((a)>(b)?(a):(b))
 8 #define min(a,b) ((a)<(b)?(a):(b))
 9 int n,k,e,adj[N],ans,cnt[K];
10 bool vis[N];
11 int root,tot,size[N],maxs[N];
12 LL dis[N];
13 struct edge{int zhong,val,next;}s[N<<1];
14 inline void add(int qi,int zhong,int val)
15     {s[++e].zhong=zhong;s[e].val=val;s[e].next=adj[qi];adj[qi]=e;}
16 inline void dfs1(int rt,int fa)
17 {
18     size[rt]=1,maxs[rt]=0;
19     for(int i=adj[rt];i;i=s[i].next)
20         if(!vis[s[i].zhong]&&s[i].zhong!=fa)
21             dfs1(s[i].zhong,rt),size[rt]+=size[s[i].zhong],maxs[rt]=max(maxs[rt],size[s[i].zhong]);
22     maxs[rt]=max(maxs[rt],tot-size[rt]);
23     if(maxs[rt]<maxs[root])root=rt;
24 }
25 inline void dfs2(int rt,int fa,int deep)
26 {
27     if(dis[rt]>=0&&dis[rt]<=k)ans=min(ans,deep+cnt[k-dis[rt]]);
28     for(int i=adj[rt];i;i=s[i].next)
29         if(!vis[s[i].zhong]&&s[i].zhong!=fa)
30             dis[s[i].zhong]=dis[rt]+s[i].val,dfs2(s[i].zhong,rt,deep+1);
31 }
32 inline void update(int rt,int fa,int deep,bool opt)
33 {
34     if(dis[rt]>=0&&dis[rt]<=k)
35         if(opt)cnt[dis[rt]]=min(cnt[dis[rt]],deep);
36         else cnt[dis[rt]]=n;
37     for(int i=adj[rt];i;i=s[i].next)
38         if(!vis[s[i].zhong]&&s[i].zhong!=fa)
39             update(s[i].zhong,rt,deep+1,opt);
40 }
41 inline void solve(int rt)
42 {
43     vis[rt]=1;cnt[0]=0;
44     for(int i=adj[rt];i;i=s[i].next)
45         if(!vis[s[i].zhong])
46             dis[s[i].zhong]=s[i].val,dfs2(s[i].zhong,0,1),update(s[i].zhong,0,1,1);
47     for(int i=adj[rt];i;i=s[i].next)
48         if(!vis[s[i].zhong])
49             update(s[i].zhong,0,1,0);
50     for(int i=adj[rt];i;i=s[i].next)
51         if(!vis[s[i].zhong])
52             root=0,tot=size[s[i].zhong],dfs1(s[i].zhong,0),solve(root);
53 }
54 int main()
55 {
56     // freopen("Ark.in","r",stdin);
57     register int i,a,b,c;scanf("%d%d",&n,&k),ans=n;
58     for(i=1;i<n;++i)scanf("%d%d%d",&a,&b,&c),++a,++b,add(a,b,c),add(b,a,c);
59     for(i=1;i<=k;++i)cnt[i]=n;
60     root=0,maxs[0]=n,tot=n,dfs1(1,0),solve(root),
61     printf("%d\n",(ans==n)?-1:ans);
62 }
BZOJ2599

點分治最核心的思想就是這樣,找到重心,分別計算。其他的習題還有:

bzoj4016 (建最短路樹,再點分)

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <vector>
 4 #include <algorithm>
 5 using namespace std;
 6 #define N 30010
 7 #define inf 0x3fffffff
 8 #define max(a,b) ((a)>(b)?(a):(b))
 9 int n,m,k,e,adj[N],q[N<<4],hd,tl;bool vis[N];
10 struct edge{int to,next,val;}s[N<<1];
11 inline void add(int qi,int to,int val)
12     {s[++e].to=to;s[e].next=adj[qi];adj[qi]=e;s[e].val=val;}
13 int totsize,root,size[N],maxs[N],maxl[N],cnt[N],dis[N],ans=-inf,tot;
14 struct node{int to,val;};vector<node>to[N];
15 inline bool mt(const node &a,const node &b){return a.to<b.to;}
16 inline void dfs0(int rt)
17 {
18     vis[rt]=1;
19     for(int i=0,l=to[rt].size();i<l;++i)
20         if(!vis[to[rt][i].to]&&dis[to[rt][i].to]==dis[rt]+to[rt][i].val)
21             add(rt,to[rt][i].to,to[rt][i].val),add(to[rt][i].to,rt,to[rt][i].val),dfs0(to[rt][i].to);
22 }
23 inline void spfa_and_build()
24 {
25     memset(dis,0x3f,sizeof(dis));
26     q[hd=tl=1]=1,vis[1]=1,dis[1]=0;
27     register int i,x,l;
28     while(hd<=tl)
29         for(x=q[hd++],vis[x]=0,i=0,l=to[x].size();i<l;++i)
30             if(dis[to[x][i].to]>dis[x]+to[x][i].val)
31             {
32                 dis[to[x][i].to]=dis[x]+to[x][i].val;
33                 if(!vis[to[x][i].to])vis[to[x][i].to]=1,q[++tl]=to[x][i].to;
34             }
35     dfs0(1);
36 }
37 inline void dfs1(int rt,int fa)
38 {
39     size[rt]=1,maxs[rt]=0;
40     for(int i=adj[rt];i;i=s[i].next)
41         if(!vis[s[i].to]&&s[i].to!=fa)
42             dfs1(s[i].to,rt),size[rt]+=size[s[i].to],maxs[rt]=max(maxs[rt],size[s[i].to]);
43     maxs[rt]=max(maxs[rt],totsize-size[rt]);
44     if(maxs[rt]<maxs[root])root=rt;
45 }
46 inline void dfs2(int rt,int fa,int dp)
47 {
48     if(maxl[k-dp-1]!=-inf)
49         if(ans<dis[rt]+maxl[k-dp-1])ans=dis[rt]+maxl[k-dp-1],tot=cnt[k-dp-1];
50         else if(ans==dis[rt]+maxl[k-dp-1])tot+=cnt[k-dp-1];
51     if(dp+1<k)for(int i=adj[rt];i;i=s[i].next)
52         if(!vis[s[i].to]&&s[i].to!=fa)
53             dis[s[i].to]=dis[rt]+s[i].val,dfs2(s[i].to,rt,dp+1);
54 }
55 inline void update(int rt,int fa,int dp)
56 {
57     if(maxl[dp]<dis[rt])maxl[dp]=dis[rt],cnt[dp]=1;
58     else if(maxl[dp]==dis[rt])++cnt[dp];
59     if(dp+1<k)for(int i=adj[rt];i;i=s[i].next)
60         if(!vis[s[i].to]&&s[i].to!=fa)
61             update(s[i].to,rt,dp+1);
62 }
63 inline void solve(int rt)
64 {
65     vis[rt]=1,maxl[0]=0,cnt[0]=1;
66     for(int i=adj[rt];i;i=s[i].next)
67         if(!vis[s[i].to])dis[s[i].to]=s[i].val,dfs2(s[i].to,0,1),update(s[i].to,0,1);
68     for(int i=1;i<=k;++i)maxl[i]=-inf,cnt[i]=0;
69     // for(int i=adj[rt];i;i=s[i].next)if(!vis[s[i].to])update(s[i].to,0,1,0);
70     for(int i=adj[rt];i;i=s[i].next)
71         if(!vis[s[i].to]&&size[s[i].to]>=k)root=0,totsize=size[s[i].to],dfs1(s[i].to,0),solve(root);
72 }
73 inline void work_and_print()
74 {
75     memset(vis,0,sizeof(vis)),memset(dis,0,sizeof(dis)),memset(cnt,0,sizeof(cnt));
76     for(int i=1;i<=k;++i)maxl[i]=-inf;
77     totsize=n,root=0,maxs[0]=inf,dfs1(1,0),solve(root);
78     printf("%d %d",ans,tot);
79 }
80 int main()
81 {
82     scanf("%d%d%d",&n,&m,&k);
83     register int i,j,l,a,b,c;
84     for(i=1;i<=m;++i)
85         scanf("%d%d%d",&a,&b,&c),to[a].push_back((node){b,c}),to[b].push_back((node){a,c});
86     for(i=1;i<=n;++i)sort(to[i].begin(),to[i].end(),mt);
87     spfa_and_build(),work_and_print();
88 }
BZOJ4016

bzoj3672 (推式子,點分治+CDQ維護凸殼)

bzoj1758 (01分數規划,卡精卡時……)

二.從點分治到動態點分治

做着做着題,你可能會發現有的題目要修改……這可怎么辦呢。
我們肯定不能一次修改跑一次DP或者點分治吧……
那么我們考慮用數組或者數據結構來維護我們需要的信息,每一次修改都維護數據結構中對應的信息。
這其實借用了樹規中的思想,我們通過維護這些信息,可以實現點之間$O(1)$或者$O(logn)$的轉移。
可是如果我們對原樹中的點維護這些數據結構,一條鏈就可以送我們上天……
但同時我們觀察到,如果我們把點分治時每一層的重心之間連邊,這就構成了一顆高度為$logn$的新樹,我們叫它分治樹。
那么我們就用數據結構維護分治樹種對應我們需要的信息。
一個常用的思想:維護當前子樹中到父親的某些信息,從而減小查詢的復雜度……
這樣說可能有些抽象……我們來看道題:

1095: [ZJOI2007]Hide 捉迷藏

Time Limit: 40 Sec  Memory Limit: 256 MB

Description

  捉迷藏 Jiajia和Wind是一對恩愛的夫妻,並且他們有很多孩子。某天,Jiajia、Wind和孩子們決定在家里玩
捉迷藏游戲。他們的家很大且構造很奇特,由N個屋子和N-1條雙向走廊組成,這N-1條走廊的分布使得任意兩個屋
子都互相可達。游戲是這樣進行的,孩子們負責躲藏,Jiajia負責找,而Wind負責操縱這N個屋子的燈。在起初的
時候,所有的燈都沒有被打開。每一次,孩子們只會躲藏在沒有開燈的房間中,但是為了增加刺激性,孩子們會要
求打開某個房間的電燈或者關閉某個房間的電燈。為了評估某一次游戲的復雜性,Jiajia希望知道可能的最遠的兩
個孩子的距離(即最遠的兩個關燈房間的距離)。 我們將以如下形式定義每一種操作: C(hange) i 改變第i個房
間的照明狀態,若原來打開,則關閉;若原來關閉,則打開。 G(ame) 開始一次游戲,查詢最遠的兩個關燈房間的
距離。

Input

  第一行包含一個整數N,表示房間的個數,房間將被編號為1,2,3…N的整數。接下來N-1行每行兩個整數a, b,
表示房間a與房間b之間有一條走廊相連。接下來一行包含一個整數Q,表示操作次數。接着Q行,每行一個操作,如
上文所示。

Output

  對於每一個操作Game,輸出一個非負整數到hide.out,表示最遠的兩個關燈房間的距離。若只有一個房間是關
着燈的,輸出0;若所有房間的燈都開着,輸出-1。

Sample Input

8
1 2
2 3
3 4
3 5
3 6
6 7
6 8
7
G
C 1
G
C 2
G
C 1
G

Sample Output

4
3
3
4

HINT

對於100%的數據, N ≤100000, M ≤500000。

 

 那么我們看這個題,如果這個題不用修改的話,$O(n)$的樹規和$O(nlogn)$的點分治都可以解決。

想一下,我們需要什么變量呢?需要最長鏈和次長鏈拼起來對吧……

那么我們可以用一些數據結構維護每個點在分治樹中伸下去與子樹中的每個黑點之間鏈的長度,

這個數據結構我們可以用大根堆,那么最長鏈就是堆頂了。

但同時,我們意識到最長鏈和次長鏈不能來自同一棵分治樹下的子樹,

因此我們再來一個數據結構,維護這個點每個分治樹兒子的堆頂(這就是分治樹中“維護父親/維護孩子”思想的體現,雖然不是很明顯),我們還可以用個堆。

那么這個堆的前兩個元素之和就是經過這個點的最長鏈長度,我們對於每個點都可以維護這樣一個變量(當然,也可能不存在)。

那么我們再用一個大根堆維護每個節點的答案,那么這個堆的堆頂就是答案了。

每次修改的時候,我們在第一類堆中刪除或者添加對應的鏈長,維護第二類堆對應子樹的元素,再更新第三類堆中的答案,就實現了一次維護。

分治樹的深度是$logn$的,堆操作的是$logn$的,因此時間復雜度為$O(nlog^{2}n)$。

代碼實現:

  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 }
BZOJ1095

其實這道題是比較簡單的……那么我們再看一道題。

3924: [Zjoi2015]幻想鄉戰略游戲

Time Limit: 100 Sec  Memory Limit: 256 MB

Description

 傲嬌少女幽香正在玩一個非常有趣的戰略類游戲,本來這個游戲的地圖其實還不算太大,幽香還能管得過來,但是不知道為什么現在的網游廠商把游戲的地圖越做越大,以至於幽香一眼根本看不過來,更別說和別人打仗了。 在打仗之前,幽香現在面臨一個非常基本的管理問題需要解決。 整個地圖是一個樹結構,一共有n塊空地,這些空地被n-1條帶權邊連接起來,使得每兩個點之間有一條唯一的路徑將它們連接起來。在游戲中,幽香可能在空地上增加或者減少一些軍隊。同時,幽香可以在一個空地上放置一個補給站。 如果補給站在點u上,並且空地v上有dv個單位的軍隊,那么幽香每天就要花費dv×dist(u,v)的金錢來補給這些軍隊。由於幽香需要補給所有的軍隊,因此幽香總共就要花費為Sigma(Dv*dist(u,v),其中1<=V<=N)的代價。其中dist(u,v)表示u個v在樹上的距離(唯一路徑的權和)。 因為游戲的規定,幽香只能選擇一個空地作為補給站。在游戲的過程中,幽香可能會在某些空地上制造一些軍隊,也可能會減少某些空地上的軍隊,進行了這樣的操作以后,出於經濟上的考慮,幽香往往可以移動他的補給站從而省一些錢。但是由於這個游戲的地圖是在太大了,幽香無法輕易的進行最優的安排,你能幫幫她嗎? 你可以假定一開始所有空地上都沒有軍隊。

PDF版試題:JudgeOnline/upload/201708/zjoi2015d1.pdf

Input

第一行兩個數n和Q分別表示樹的點數和幽香操作的個數,其中點從1到n標號。 
接下來n-1行,每行三個正整數a,b,c,表示a和b之間有一條邊權為c的邊。 
接下來Q行,每行兩個數u,e,表示幽香在點u上放了e單位個軍隊
(如果e<0,就相當於是幽香在u上減少了|e|單位個軍隊,說白了就是du←du+e)。
數據保證任何時刻每個點上的軍隊數量都是非負的。 
1<=c<=1000, 0<=|e|<=1000, n<=10^5, Q<=10^5
對於所有數據,這個樹上所有點的度數都不超過20
N,Q>=1
 

Output

 對於幽香的每個操作,輸出操作完成以后,每天的最小花費,也即如果幽香選擇最優的補給點進行補給時的花費。 

Sample Input

10 5
1 2 1
2 3 1
2 4 1
1 5 1
2 61
2 7 1
5 8 1
7 91
1 10 1
3 1
2 1
8 1
3 1
4 1

Sample Output

0
1
4
5
6
 
怎么操作呢?我們不難發現,由於目標函數是$Σd_{v}*dis(u,v)$,由定義可知,我們的目標其實就是求帶權的重心
那么怎么求呢……還是先假設我們有一棵靜態的樹,那么考慮樹規。
想一下樹規中我們都求了什么:
設$s1[i]$為i子樹點權和,$s2[i]$為i子樹點到它的距離和,$s3[i]$為i父親到它的距離和。
那么轉移應該是
$ans[i]=ans[fa]+s2[fa]-s2[i]-s1[i]*dis(i,fa)+s3[fa]+(sum-s1[i])*dis(i,fa)$
dis我們可以用ST表RMQ或者倍增LCA來求.
但其實我們是可以不要s3數組,一層一層跳着往上求$s2[fa]-s2[i]-s1[i]*dis(i,fa)$這個式子的。
由於原樹可能是條鏈,我們不敢直接跳,只好維護s3數組。
但是如果修改的話,s3涉及到的修改會特別多!
我們聯想到,分治樹的高度是$logn$的!
因此我們可以不維護s3,僅僅維護s1和s2,在分治樹上用$O(logn)$的時間求出某一個點作為帶權重心時的權和。
但是你會發現,我們上面的式子里有一個-s2[i]很難處理:在樹規中i和fa只隔了一條邊,但是在分治樹中,你並不知道元素長什么樣子!
因此我們還是得加一個s3數組,s3[i]表示i的分治樹子節點到i分治樹父親的權和。這就是我們動態樹分治中“維護父親”思想的典型體現
那么我們現在就可以以$O(logn)$的復雜度枚舉i的祖先fa,求$Σs2[fa]-s3[i]+(s1[fa]-s1[i])*dis(i,fa)$來求與i在原樹上的lca是fa的點到i的距離了。
*這種$O(logn)$跳父親的做法也是一種常見的思想。
然后對於統計答案,我們可以每次從分治樹的根出發,看往他的哪個兒子走答案更優。
我們最多走$logn$層,每一層最多進行20次$O(logn)$的計算,因此查詢是$O(20log^{2}n)$的.
總的復雜度$O(20Qlog^{2}n)$。代碼見下:
  1 #include <cstdio>
  2 #include <cstring>
  3 using namespace std;
  4 #define LL long long
  5 #define N 100010
  6 #define LL long long
  7 #define max(a,b) ((a)>(b)?(a):(b))
  8 #define min(a,b) ((a)<(b)?(a):(b))
  9 struct edge{int zhong,next,val;};
 10 struct G
 11 {
 12     edge s[N<<1];int e,adj[N];
 13     G(){e=0;memset(adj,0,sizeof(adj));}
 14     inline void add(int qi,int zhong,int val=0)
 15         {s[++e].zhong=zhong;s[e].next=adj[qi];adj[qi]=e;s[e].val=val;}
 16 }T1,T2;
 17 int n,q,f[N<<1][18],logn[N<<1],bin[25],tp;
 18 LL s[3][N],sum,ans,len[N];
 19 int dfn[N],num;
 20 inline void dfs1(int rt,int fa)
 21 {
 22     f[(dfn[rt]=++num)][0]=len[rt];
 23     for(int i=T1.adj[rt];i;i=T1.s[i].next)
 24         if(T1.s[i].zhong!=fa)
 25             len[T1.s[i].zhong]=len[rt]+T1.s[i].val,dfs1(T1.s[i].zhong,rt),f[++num][0]=len[rt];
 26 }
 27 inline LL LCA(int a,int b)
 28 {
 29     if(dfn[a]>dfn[b])a^=b,b^=a,a^=b;
 30     int k=logn[dfn[b]-dfn[a]+1];
 31     return min(f[dfn[a]][k],f[dfn[b]-bin[k]+1][k])<<1;
 32 }
 33 inline LL dis(int a,int b){return len[a]+len[b]-LCA(a,b);}
 34 int size[N],maxs[N],totsize,root,Vater[N];
 35 bool vis[N];
 36 inline void dfs2(int rt,int fa)
 37 {
 38     size[rt]=1,maxs[rt]=0;
 39     for(int i=T1.adj[rt];i;i=T1.s[i].next)
 40         if(!vis[T1.s[i].zhong]&&T1.s[i].zhong!=fa)
 41             dfs2(T1.s[i].zhong,rt),size[rt]+=size[T1.s[i].zhong],
 42             maxs[rt]=max(size[T1.s[i].zhong],maxs[rt]);
 43     maxs[rt]=max(totsize-size[rt],maxs[rt]);
 44     if(maxs[rt]<maxs[root])root=rt;
 45 }
 46 inline void dfs3(int rt,int fa)
 47 {
 48     Vater[rt]=fa;vis[rt]=1;int siz=totsize;
 49     for(int i=T1.adj[rt];i;i=T1.s[i].next)
 50         if(!vis[T1.s[i].zhong])
 51         {
 52             if(size[T1.s[i].zhong]>size[rt])totsize=siz-size[rt];
 53             else totsize=size[T1.s[i].zhong];
 54             root=0,dfs2(T1.s[i].zhong,0),T2.add(rt,root,T1.s[i].zhong),dfs3(root,rt);
 55         }
 56 }
 57 inline void update(int who,LL val)
 58 {
 59     for(int rt=who;rt;rt=Vater[rt])
 60     {
 61         s[0][rt]+=val,s[1][rt]+=dis(rt,who)*val;
 62         if(Vater[rt])s[2][rt]+=dis(Vater[rt],who)*val;
 63     }
 64 }
 65 inline LL calc(int rt)
 66 {
 67     LL ret=0;
 68     for(int x=rt;x;x=Vater[x])
 69     {
 70         ret+=s[1][x];
 71         if(Vater[x])ret+=-s[2][x]+(s[0][Vater[x]]-s[0][x])*dis(Vater[x],rt);
 72     }
 73     return ret;
 74 }
 75 inline LL getans(int rt)
 76 {
 77     LL temp=calc(rt);
 78     for(int i=T2.adj[rt];i;i=T2.s[i].next)
 79         if(calc(T2.s[i].val)<temp)return getans(T2.s[i].zhong);//*******
 80     return temp;
 81 }
 82 char B[1<<15],*S=B,*T=B;
 83 #define getc ( S==T&&(T=(S=B)+fread(B,1,1<<15,stdin),S==T)?0:*S++)
 84 inline int read()
 85 {
 86     int x=0,f=1;register char c=getc;
 87     while(c<'0'||c>'9'){if(c=='-')f=-1;c=getc;}
 88     while(c>='0'&&c<='9')x=10*x+(c^48),c=getc;
 89     return x*f;
 90 }
 91 int main()
 92 {
 93     register int i,j,orig,a,b,c;
 94     n=read();q=read();
 95     for(bin[0]=i=1;i<=20;++i)bin[i]=bin[i-1]<<1;
 96     while(bin[tp+1]<=(n<<1))++tp;
 97     for(logn[0]=-1,i=1;i<=(n<<1);++i)logn[i]=logn[i>>1]+1;
 98     for(i=1;i<n;++i)
 99         a=read(),b=read(),c=read(),T1.add(a,b,c),T1.add(b,a,c);
100     dfs1(1,0),root=0,maxs[0]=n+1,totsize=n,dfs2(1,0);
101     for(i=1;i<=tp;++i)
102         for(j=1;j+bin[i]-1<=(n<<1);++j)
103             f[j][i]=min(f[j][i-1],f[j+bin[i-1]][i-1]);
104     orig=root,dfs3(root,0);
105     while(q--)
106         a=read(),b=read(),update(a,b),printf("%lld\n",getans(orig));
107 }
BZOJ4012

 我們再來一道大同小異的題目:

4012: [HNOI2015]開店

Time Limit: 70 Sec  Memory Limit: 512 MB

Description

 風見幽香有一個好朋友叫八雲紫,她們經常一起看星星看月亮從詩詞歌賦談到

人生哲學。最近她們靈機一動,打算在幻想鄉開一家小店來做生意賺點錢。這樣的
想法當然非常好啦,但是她們也發現她們面臨着一個問題,那就是店開在哪里,面
向什么樣的人群。很神奇的是,幻想鄉的地圖是一個樹形結構,幻想鄉一共有 n
個地方,編號為 1 到 n,被 n-1 條帶權的邊連接起來。每個地方都住着一個妖怪,
其中第 i 個地方的妖怪年齡是 x_i。妖怪都是些比較喜歡安靜的家伙,所以它們並
不希望和很多妖怪相鄰。所以這個樹所有頂點的度數都小於或等於 3。妖怪和人一
樣,興趣點隨着年齡的變化自然就會變化,比如我們的 18 歲少女幽香和八雲紫就
比較喜歡可愛的東西。幽香通過研究發現,基本上妖怪的興趣只跟年齡有關,所以
幽香打算選擇一個地方 u(u為編號),然后在 u開一家面向年齡在 L到R 之間(即
年齡大於等於 L、小於等於 R)的妖怪的店。也有可能 u這個地方離這些妖怪比較
遠,於是幽香就想要知道所有年齡在 L 到 R 之間的妖怪,到點 u 的距離的和是多
少(妖怪到 u 的距離是該妖怪所在地方到 u 的路徑上的邊的權之和) ,幽香把這個
稱為這個開店方案的方便值。幽香她們還沒有決定要把店開在哪里,八雲紫倒是准
備了很多方案,於是幽香想要知道,對於每個方案,方便值是多少呢。
 

Input

 第一行三個用空格分開的數 n、Q和A,表示樹的大小、開店的方案個數和妖

怪的年齡上限。 
第二行n個用空格分開的數 x_1、x_2、…、x_n,x_i 表示第i 個地點妖怪的年
齡,滿足0<=x_i<A。(年齡是可以為 0的,例如剛出生的妖怪的年齡為 0。) 
接下來 n-1 行,每行三個用空格分開的數 a、b、c,表示樹上的頂點 a 和 b 之
間有一條權為c(1 <= c <= 1000)的邊,a和b 是頂點編號。 
接下來Q行,每行三個用空格分開的數 u、 a、 b。對於這 Q行的每一行,用 a、
b、A計算出 L和R,表示詢問“在地方 u開店,面向妖怪的年齡區間為[L,R]的方
案的方便值是多少”。對於其中第 1 行,L 和 R 的計算方法為:L=min(a%A,b%A), 
R=max(a%A,b%A)。對於第 2到第 Q行,假設前一行得到的方便值為 ans,那么當
前行的 L 和 R 計算方法為: L=min((a+ans)%A,(b+ans)%A), 
R=max((a+ans)%A,(b+ans)%A)。 
 

Output

對於每個方案,輸出一行表示方便值。 

 

Sample Input

10 10 10
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

1603
957
7161
9466
3232
5223
1879
1669
1282
0

HINT

 滿足 n<=150000,Q<=200000。對於所有數據,滿足 A<=10^9

 

現在你應該能想到怎么做了吧!

我們用數據結構維護以i為分治樹根的子樹每個年齡的妖怪到i的距離,以及到i分治樹父親的距離,然后用數據結構求和。

數據結構用什么呢?線段樹?$O(nlog^{2}n)$的空間復雜度肯定會MLE的。

注意到這個求和可以寫為前綴和相減的形式,因此我們用可以用$O(nlogn)$的空間復雜度的vector+sort排序節省內存。

那么這道題就被解決啦……代碼見下:

  1 #include <cstdio>
  2 #include <cstring>
  3 #include <vector>
  4 #include <algorithm>
  5 using namespace std;
  6 #define N 150010
  7 #define LL long long
  8 int n,e,tot,adj[N],q,maxn,val[N];
  9 char B[1<<15],*S=B,*T=B;
 10 #define getc (S==T&&(T=(S=B)+fread(B,1,1<<15,stdin),S==T)?0:*S++)
 11 inline int read()
 12 {
 13     int x=0;register char c=getc;
 14     while(c<'0'||c>'9')c=getc;
 15     while(c>='0'&&c<='9')x=10*x+(c^48),c=getc;
 16     return x;
 17 }
 18 #define min(a,b) ((a)<(b)?(a):(b))
 19 #define max(a,b) ((a)>(b)?(a):(b))
 20 struct edge{int zhong,next,val;}s[N<<1];
 21 inline void add(int qi,int zhong,int val)
 22     {s[++e].zhong=zhong,s[e].val=val,s[e].next=adj[qi],adj[qi]=e;}
 23 int f[N<<1][19],deep[N],dfn[N],num,bin[25],tp,logn[N<<1];
 24 inline void ST()
 25 {
 26     for(int i=1;i<=tp;++i)
 27         for(int j=1;j+bin[i]-1<=(n<<1);++j)
 28             f[j][i]=min(f[j][i-1],f[j+bin[i-1]][i-1]);
 29 }
 30 inline void dfs1(int rt,int fa,int len)
 31 {
 32     f[(dfn[rt]=++num)][0]=deep[rt]=len;
 33     for(int i=adj[rt];i;i=s[i].next)
 34         if(s[i].zhong!=fa)
 35             dfs1(s[i].zhong,rt,len+s[i].val),f[++num][0]=len;
 36 }
 37 int Vater[N],size[N],maxs[N],totsize,root;
 38 bool vis[N];
 39 inline void dfs2(int rt,int fa)
 40 {
 41     size[rt]=1,maxs[rt]=0;
 42     for(int i=adj[rt];i;i=s[i].next)
 43         if(!vis[s[i].zhong]&&s[i].zhong!=fa)
 44             dfs2(s[i].zhong,rt),size[rt]+=size[s[i].zhong],
 45             maxs[rt]=max(maxs[rt],size[s[i].zhong]);
 46     maxs[rt]=max(maxs[rt],totsize-size[rt]);
 47     if(maxs[rt]<maxs[root])root=rt;
 48 }
 49 inline LL dis(int a,int b)
 50 {
 51     if(dfn[a]>dfn[b])a^=b,b^=a,a^=b;
 52     int k=logn[dfn[b]-dfn[a]+1];
 53     return deep[a]+deep[b]-(min(f[dfn[a]][k],f[dfn[b]-bin[k]+1][k])<<1);
 54 }
 55 struct node
 56 {
 57     int val;LL size[3];
 58     node(int a=0,LL b=0,LL c=0,LL d=0){val=a,size[0]=b,size[1]=c,size[2]=d;}
 59     inline bool operator < (const node &b)const
 60         {return val<b.val;}
 61 };
 62 vector<node>sta[N];
 63 inline void dfs3(int rt,int fa,int Vatti)
 64 {
 65     sta[Vatti].push_back(node(val[rt],1,dis(rt,Vatti), Vater[Vatti]?dis(rt,Vater[Vatti]):0 ));
 66     for(int i=adj[rt];i;i=s[i].next)
 67         if(s[i].zhong!=fa&&!vis[s[i].zhong])
 68             dfs3(s[i].zhong,rt,Vatti);
 69 }
 70 inline void dfs4(int rt,int fa)
 71 {
 72     Vater[rt]=fa,vis[rt]=1;
 73     int siz=totsize;
 74     dfs3(rt,0,rt);sta[rt].push_back(node(-1,0,0,0));
 75     sort(sta[rt].begin(),sta[rt].end());
 76     for(int i=0,j=sta[rt].size();i<j-1;++i)
 77         sta[rt][i+1].size[0]+=sta[rt][i].size[0],
 78         sta[rt][i+1].size[1]+=sta[rt][i].size[1],
 79         sta[rt][i+1].size[2]+=sta[rt][i].size[2];
 80     for(int i=adj[rt];i;i=s[i].next)
 81         if(!vis[s[i].zhong])
 82         {
 83             if(size[s[i].zhong]>size[rt])totsize=siz-size[rt];
 84             else totsize=size[s[i].zhong];
 85             root=0,dfs2(s[i].zhong,0),dfs4(root,rt);
 86         }
 87 }
 88 inline node query(int id,int l,int r)
 89 {
 90     if(id==0)return node();
 91     vector<node>::iterator it1=upper_bound(sta[id].begin(),sta[id].end(),node(r,0,0,0) );--it1;
 92     vector<node>::iterator it2=upper_bound(sta[id].begin(),sta[id].end(),node(l-1,0,0,0));--it2;
 93     return node(0,it1->size[0]-it2->size[0],it1->size[1]-it2->size[1],it1->size[2]-it2->size[2]);
 94 }
 95 inline LL calc(int rt,int l,int r)
 96 {
 97     LL ret=0;
 98     // printf("rt=%d l=%d r=%d\n",rt,l,r);
 99     for(int x=rt;x;x=Vater[x])
100     {
101         node a=query(x,l,r);
102         ret+=a.size[1];
103         if(x!=rt)ret+=a.size[0]*dis(x,rt);
104         if(Vater[x])ret-=a.size[2]+a.size[0]*dis(Vater[x],rt);
105     }
106     return ret;
107 }
108 int main()
109 {
110     register int i,j,a,b,c;LL ans=0;
111     n=read(),q=read(),maxn=read();
112     for(bin[0]=i=1;i<=20;++i)bin[i]=bin[i-1]<<1;
113     while(bin[tp+1]<=(n<<1))++tp;
114     for(logn[0]=-1,i=1;i<=(n<<1);++i)logn[i]=logn[i>>1]+1;
115     for(i=1;i<=n;++i)val[i]=read();
116     for(i=1;i<n;++i)
117         a=read(),b=read(),c=read(),add(a,b,c),add(b,a,c);
118     dfs1(1,0,0),ST();
119     root=0,maxs[0]=n+1,totsize=n,dfs2(1,0);
120     dfs4(root,0);
121     while(q--)
122     {
123         a=read(),b=read(),c=read();
124         b=(b+ans)%maxn,c=(c+ans)%maxn;
125         if(b>c)b^=c,c^=b,b^=c;
126         printf("%lld\n",ans=calc(a,b,c));
127     }
128 }
BZOJ4012

最后我們來個大boss試試知難♂而上

 

3435: [Wc2014]紫荊花之戀

Time Limit: 240 Sec  Memory Limit: 512 MB

Description

強強和萌萌是一對好朋友。有一天他們在外面閑逛,突然看到前方有一棵紫荊樹。這已經是紫荊花飛舞的季節了,無數的花瓣以肉眼可見的速度從紫荊樹上長了出來。仔細看看的話,這個大樹實際上是一個帶權樹。每個時刻它會長出一個新的葉子節點。每個節點上有一個可愛的小精靈,新長出的節點上也會同時出現一個新的小精靈。小精靈是很萌但是也很脆弱的生物,每個小精靈 i 都有一個感受能力值Ri ,小精靈 i, j 成為朋友當且僅當在樹上 i 和 j 的距離 dist(i,j) ≤ Ri + R! ,其中 dist(i, j)表示在這個樹上從 i 到 j 的唯一路徑上所有邊的邊權和。強強和萌萌很好奇每次新長出一個葉子節點之后,這個樹上總共有幾對朋友。  
我們假定這個樹一開始為空,節點按照加入的順序從 1開始編號。由於強強非常好奇, 你必須在他每次出現新節點后馬上給出總共的朋友對數,不能拖延哦。 

Input

共有 n + 2 行。 
第一行包含一個正整數,表示測試點編號。 
第二行包含一個正整數 n ,表示總共要加入的節點數。 
我們令加入節點前的總共朋友對數是 last_ans,在一開始時它的值為0。 
接下來 n 行中第 i 行有三個數 ai, bi, ri,表示節點  i  的父節點的編號為 ai xor (last_ans mod 10^9)   (其中xor 表示異或,mod  表示取余,數據保證這樣操作后得到的結果介於 1到i  –  1之間),與父節點之間的邊權為 ci,節點 i 上小精靈的感受能力值為r!。 
注意 a1 = c1 = 0,表示 1 號點是根節點,對於 i > 1,父節點的編號至少為1。

Output

包含 n 行,每行輸出1 個整數, 表示加入第 i 個點之后,樹上有幾對朋友。

Sample Input

0
5
0 0 6
1 2 4
0 9 4
0 5 5
0 2 4 

Sample Output

0
1
2
4

HINT

1<=Ci<=10000
Ai<=2*10^9
Ri<=10^9
N<=100000

 

那么這題怎么做呢……

我們先考慮把題設式子變形:

\[dis(u,v)<=r_{u}+r_{v}  \]

\[dis(u,lca)+dis(lca,v)<=r_{u}+r_{v}  \]

\[r_{u}-dis(lca,u)>=dis(lca,v)-r_{v}\]

那么接下來我們沿用上面幾題的思想,在原樹上每個節點開2棵平衡樹,維護以i為根的子樹中的$dis(i,u)-r_{u}$值和$dis(fa[i],u)-r_{u}$值。

這樣每次我們從新插入的節點開始往上爬到根,並且更新答案即可。

但是我們發現,如果這是條鏈……時間復雜度會被卡到$O(n^{2})$

但是我們轉念一想,點分樹是相對平衡的,深度是$logn$級別的呀!

所以我們借用替罪羊拍扁重建的思想,如果在插入結束后,

某個節點的某個兒子特別大(用$alpha$判斷),我們就把那棵子樹重構為一棵分治樹。

這樣我們的復雜度可以有$O(nlog^{2}n)$的保障。

在代碼實現的時候,要特別注意重構時和原來這個子樹的分治樹父親之間信息的維護。

代碼:

  1 #include <cstdio>
  2 #include <cstring>
  3 #include <cstdlib>
  4 #include <vector>
  5 using namespace std;
  6 #define LL long long
  7 #define inf 1000000000
  8 #define N 100010
  9 #define alpha 0.755
 10 int n,e,adj[N],val[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 vector<int> to[N];
 15 int f[N][18],bin[25],tp,deep[N],len[N];
 16 inline int LCA(int a,int b)
 17 {
 18     if(deep[a]<deep[b])a^=b,b^=a,a^=b;
 19     int i,cha=deep[a]-deep[b];
 20     for(i=tp;~i;--i)if(cha&bin[i])a=f[a][i];
 21     if(a==b)return a;
 22     for(i=tp;~i;--i)if(f[a][i]!=f[b][i])a=f[a][i],b=f[b][i];
 23     return f[a][0];
 24 }
 25 inline int dis(int a,int b){return len[a]+len[b]-len[LCA(a,b)]*2;}
 26 struct Goat
 27 {
 28     int val,size;Goat *ch[2];
 29     Goat(){}
 30     inline bool bad()
 31         {return ch[0]->size>=size*alpha+5 || ch[1]->size>=size*alpha+5; }
 32 }*tree1[N],*tree2[N],mem[N<<8],*pool[N<<8],*null,*sta[N];
 33 int tot,top;
 34 inline void intn()
 35 {
 36     null=new Goat();
 37     null->ch[0]=null->ch[1]=null,null->val=null->size=0;
 38     for(int i=0;i<(N<<8);++i)pool[i]=mem+i;
 39     tot=(N<<8)-1;
 40     for(int i=0;i<=n;++i)tree1[i]=tree2[i]=null;
 41 }
 42 inline Goat** insert(Goat *&a,int val)
 43 {
 44     if(a==null)
 45     {
 46         a=pool[tot--],a->ch[0]=a->ch[1]=null;
 47         a->val=val,a->size=1;return &null;
 48     }
 49     ++a->size;
 50     Goat **o=insert(a->ch[a->val<val],val);
 51     if(a->bad())o=&a;return o;
 52 }
 53 inline int get_rank(Goat *o,int val)
 54 {
 55     if(o==null)return 0;
 56     return (o->val>=val)?get_rank(o->ch[0],val):(get_rank(o->ch[1],val)+o->ch[0]->size+1);
 57 }
 58 inline void Erholung(Goat *o)
 59 {
 60     if(o==null)return;
 61     if(o->ch[0]!=null)Erholung(o->ch[0]);
 62     pool[++tot]=o;
 63     if(o->ch[1]!=null)Erholung(o->ch[1]);
 64 }
 65 inline void travel(Goat *o)
 66 {
 67     if(o==null)return;
 68     if(o->ch[0]!=null)travel(o->ch[0]);
 69     sta[++top]=o;
 70     if(o->ch[1]!=null)travel(o->ch[1]);
 71 }
 72 inline Goat* build(int l,int r)
 73 {
 74     if(l>r)return null;
 75     int mi=l+r>>1;
 76     Goat *o=sta[mi];o->size=r-l+1;
 77     o->ch[0]=build(l,mi-1),o->ch[1]=build(mi+1,r);
 78     return o;
 79 }
 80 inline void rebuild(Goat *&o){top=0,travel(o),o=build(1,top);}
 81 inline void Insert(Goat *&a,int val)
 82 {
 83     Goat **o=insert(a,val);
 84     if(*o!=null)rebuild(*o);
 85 }
 86 int size[N],maxs[N],totsize,root,Vater[N];
 87 #define max(a,b) ((a)>(b)?(a):(b))
 88 #define min(a,b) ((a)<(b)?(a):(b))
 89 bool vis[N];
 90 inline void dfs1(int rt,int fa)
 91 {
 92     size[rt]=1,maxs[rt]=0;
 93     for(int i=adj[rt];i;i=s[i].next)
 94         if(!vis[s[i].zhong]&&s[i].zhong!=fa)
 95             dfs1(s[i].zhong,rt),size[rt]+=size[s[i].zhong],
 96             maxs[rt]=max(maxs[rt],size[s[i].zhong]);
 97     maxs[rt]=max(maxs[rt],totsize-size[rt]);
 98     if(maxs[rt]<maxs[root])root=rt;
 99 }
100 inline void dfs2(int rt,int fa,int Vatti)
101 {
102     Insert(tree1[Vatti],dis(rt,Vatti)-val[rt]);
103     if(Vater[Vatti])Insert(tree2[Vatti],dis(rt,Vater[Vatti])-val[rt]);
104     for(int i=adj[rt];i;i=s[i].next)
105         if(s[i].zhong!=fa&&!vis[s[i].zhong])
106             dfs2(s[i].zhong,rt,Vatti);
107 }
108 inline void dfs3(int rt,int fa)
109 {
110     Vater[rt]=fa,vis[rt]=1;
111     int siz=totsize;
112     dfs2(rt,0,rt);
113     for(int i=adj[rt];i;i=s[i].next)
114         if(!vis[s[i].zhong])
115         {
116             if(size[s[i].zhong]>size[rt])totsize=siz-size[rt];
117             else totsize=size[s[i].zhong];
118             root=0,dfs1(s[i].zhong,rt),
119             to[rt].push_back(root),dfs3(root,rt);
120         }
121 }
122 inline void recover(int x)
123 {
124     ++totsize,vis[x]=0,
125     Erholung(tree1[x]),Erholung(tree2[x]),
126     tree1[x]=tree2[x]=null;
127     for(int i=0,k=to[x].size();i<k;++i)recover(to[x][i]);
128     to[x].clear();
129 }
130 inline void rebuild(int x)
131 {
132     totsize=0,recover(x),root=0,dfs1(x,0);
133     if(Vater[x])for(int i=0,j=to[Vater[x]].size();i<j;++i)
134         if(to[Vater[x]][i]==x)to[Vater[x]][i]=root;
135     dfs3(root,Vater[x]);
136 }
137 LL ans=0;
138 inline int insert(int x)
139 {
140     int rt,ds,ret=0;
141     for(rt=x;Vater[rt];rt=Vater[rt])
142         ds=val[x]-dis(x,Vater[rt])+1,ans+=get_rank(tree1[Vater[rt]],ds)-get_rank(tree2[rt],ds);
143     for(rt=x;rt;rt=Vater[rt])
144     {
145         Insert(tree1[rt],dis(x,rt)-val[x]);
146         if(Vater[rt])Insert(tree2[rt],dis(x,Vater[rt])-val[x]);
147     }
148     for(rt=x;Vater[rt];rt=Vater[rt])
149         if(tree1[rt]->size>=tree1[Vater[rt]]->size*alpha+5)ret=Vater[rt];//*****
150     return ret;
151 }b 
152 char B[1<<15],*S=B,*T=B;
153 #define getc (S==T&&(T=(S=B)+fread(B,1,1<<15,stdin),S==T)?0:*S++)
154 inline int read()
155 {
156     int x=0;register char c=getc;
157     while(c<'0'||c>'9')c=getc;
158     while(c>='0'&&c<='9')x=10*x+(c^48),c=getc;
159     return x;
160 }
161 int main()
162 {
163     n=read(),n=read();
164     register int i,j,x,b;
165     for(bin[0]=i=1;i<=20;++i)bin[i]=bin[i-1]<<1;
166     while(bin[tp+1]<=n)++tp;
167     maxs[0]=inf,root=0,intn();
168     for(i=1;i<=n;++i)
169     {
170         Vater[i]=f[i][0]=read()^(ans%inf),b=read(),val[i]=read();
171         deep[i]=deep[f[i][0]]+1,len[i]=len[f[i][0]]+b;vis[i]=1;
172         if(Vater[i])to[Vater[i]].push_back(i),add(f[i][0],i),add(i,f[i][0]);
173         for(int j=1;bin[j]+1<=deep[i];++j)f[i][j]=f[f[i][j-1]][j-1];
174         x=insert(i);if(x)rebuild(x);
175         printf("%lld\n",ans);
176     }
177 }
BZOJ3435

三.總結

點分治和動態樹分治其實是對樹規思想的變形。我們利用分治樹高度很小的優點,對每個點維護相應的信息,

通過$O(logn)$的額外復雜度來進行普通樹規無法完成的操作。希望本文能對你有幫助!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM