LCA(Lowest Common Ancestors),即最近公共祖先
在一棵樹上,兩個節點的深度最淺的公共祖先就是
(本身能夠是本身的祖先)
很簡單,咱們能夠快速地進入正題了html
下圖中的樹, 和 的 爲2, 和 的 是1(1也算1的祖先)node
除了是直接求兩點的 的模板題,沒有題目會叫你打個模板c++
那麼LCA能幹嗎?web
其實 的用途很廣,感受用 最多的像是這樣子的題算法
給你一棵樹 (或者是一個圖求完MST,這很常見) 和樹上的兩點數組
而後找出這兩點之間的路徑的極值或權值和或其餘東東數據結構
這時咱們能夠藉助這兩點的LCA做爲中轉點來輕鬆地解決這類題目app
仍是這棵樹svg
和 的 是1,這兩點間的路徑是下圖標紅的路徑學習
怎麼求LCA呢?
暴力dfs一遍,時間複雜度
侮辱智商
(逃)
模擬,從兩點開始一塊兒向上跳一步,跳過的點標記一下,時間複雜度
侮辱智商*2
(逃)
用dfs記錄每一個點的深度,求
時先調至統一深度,再一塊兒向上跳
若是這棵樹是一個恰好分紅兩叉的樹,時間複雜度
侮辱智商*3
(逃)
上面的都是小學生都會的東西,沒必要講了
想真正用有意義的
,下面纔是重點
求 有一種很是容易理解的方法就是tarjan算法預處理離線解決,時間複雜度 ,飛速
仍感受離線算法不適用於全部題目
(好比某些良心出題人喪病地要你強制在線,GG)
反正在線算法能夠解決離線能作出來全部的題
因此,我不講
求
了,推薦一篇寫的很棒的blog
接下來的幾個在線算法纔是搞
的門檻
這種方法的思想,就是將LCA問題轉化成RMQ問題
這裏有棵樹
如何預處理呢?
咱們用dfs遍歷一次,獲得一個 序(兒子節點回到父親節點還要再算一遍)
序就是這樣的1->2->4->7->4->8->4->2->5->2->6->9->6->10->6->2->1->3->1
(一開始在 向兒子節點走,到了葉子節點就向另外一個兒子走,最後回到 )
預處理的時間複雜度爲
設
表示
在
序當中第一次出現的位置,
表示
的深度
若是求
和
的
,r[x]~r[y]這一段區間內必定有
,並且必定是區間中深度最小的那個點
(好比上面的dfs序中,第一個 和第一個 之間的序列裏的深度最小的點是 ,而 正是 和 的 !)
遍歷以 爲根的樹時,不遍歷完全部以 爲根的樹的節點是不會回到 的
還有就是明顯地,想到達x再到y,必須上溯通過它們的LCA(兩個點之間有且只有一條路徑)
因此 的深度必定最小
直接用RMQ——ST表維護這個東西,求出來的最小值的點即爲
序的長度是 ,用 處理出 、 和 序以後直接套上裸的
設 表示dfs序中j~j+2^i-1的點當中,depth值最小的是哪一個點 便可
那麼單次詢問時間複雜度
完美
這段代碼是我co過來的,由於我也是看別人博客學的
但這代碼是真的醜
#include <cstdio> #include <cstring> #include <cmath> using namespace std; int n,_n,m,s;//_n是用來放元素進dfs序裏,最終_n=2n-1 struct EDGE { int to; EDGE* las; }e[1000001];//前向星存邊 EDGE* last[500001]; int sx[1000001];//順序,爲dfs序 int f[21][1000001];//用於ST算法 int deep[500001];//深度 int r[500001];//第一次出現的位置 int min(int x,int y) { return deep[x]<deep[y]?x:y; } void dfs(int t,int fa,int de) { sx[++_n]=t; r[t]=_n; deep[t]=de; EDGE*ei; for (ei=last[t];ei;ei=ei->las) if (ei->to!=fa) { dfs(ei->to,t,de+1); sx[++_n]=t; } } int query(int l,int r) { if (l>r) { //交換 l^=r; r^=l; l^=r; } int k=int(log2(r-l+1)); return min(f[k][l],f[k][r-(1<<k)+1]); } int main() { scanf("%d%d%d",&n,&m,&s); int j=0,x,y; for (int i=1;i<n;++i) { scanf("%d%d",&x,&y); e[++j]={y,last[x]}; last[x]=e+j; e[++j]={x,last[y]}; last[y]=e+j; } dfs(s,0,0); //如下是ST算法 for (int i=1;i<=_n;++i)f[0][i]=sx[i]; int ni=int(log2(_n)),nj,tmp; for (int i=1;i<=ni;++i) { nj=_n+1-(1<<i); tmp=1<<i-1; for (j=1;j<=nj;++j) f[i][j]=min(f[i-1][j],f[i-1][j+tmp]); } //如下是詢問,對於每次詢問,能夠O(1)回答 while (m--) { scanf("%d%d",&x,&y); printf("%d\n",query(r[x],r[y])); } }
基礎的
知識你應該已經會了吧
的運用挺廣吧,感受和線段樹的考頻有的一比,不掌握是會吃虧的
上面的是比較普通的方法求
,其實樹上倍增也能求
接下來到喪心病狂 (其實很簡單) 的樹上倍增了
倍增這個東東嚴格來講就是種思想,只可意會不可言傳
倍增,是根據已經獲得了的信息,將考慮的範圍擴大,從而加速操做的一種思想
使用了倍增思想的算法有
- 歸併排序
- 快速冪
- 基於ST表的RMQ算法
- 樹上倍增找LCA等
- FFT、後綴數組等高級算法
樹上倍增和
是比較類似的,都採用了二進制的思想,因此時空複雜度低
其實樹上倍增和樹鏈剖分在樹題裏面都用的不少
兩個都十分有趣,但倍增有着顯而易見的優勢——
比起樹鏈剖分,樹上倍增代碼短,查錯方便,時空複雜度優(都是 )
只是功能欠缺了一些不要太在乎
即便如此,樹上倍增也可以解決大部分的樹型題目反正兩個都要學
怎麼樣預處理以達到在線單次詢問 的時間呢?
咱們須要構造倍增數組
設 表示i節點的第2^j個祖先
注意 數組開到 ,由於樹上任意一點最多有 個祖先
能夠發現這個東西能夠代替不路徑壓縮的並查集,
(若
則說明
的第
祖先不存在)
而後,倍增的性質 (DP方程) 就清楚地出來了
用文字來表達它就是這樣的
i的第 個父親是i的第 個父親的第 個父親
神奇不?
就是說暴力求
的第
個祖先的時間複雜度是
,如今變成了
同時,用一個dfs處理出每一個點的深度
和
,時間複雜度
預處理的時間複雜度即爲
procedure dfs(x,y:longint); var now:longint; begin now:=last[x]; while now<>0 do begin if b[now]<>y then begin anc[b[now],0]:=x; depth[b[now]]:=depth[x]+1; dfs(b[now],x); end; now:=next[now]; end; end;
for j:=1 to trunc(ln(n)/ln(2)) do for i:=1 to n do anc[i,j]:=anc[anc[i,j-1],j-1];
請務必認認真真地閱讀如下內容不然樹上倍增你就學不會了
樹上倍增的運用中,最簡單最實用的就是求 了
還記得前面三種腦殘 求 的第三個方法嗎?
用dfs記錄每一個點的深度,求LCA時先調至統一深度,再一塊兒向上跳
其實樹上倍增運用的就是這個思想!只不過期間複雜度降至了飛快的
對於兩個節點 和 ,咱們先把 和 調至同一深度
若此時 ,那麼原來兩點的 即爲當前點
若是 但 ,就說明 在更淺的地方
咱們同時把 和 向上跳 步( ),直到
明顯這種方法確定能求出
,由於
和
必定會相遇
倍增比那種腦殘方法優的是,腦殘方法一步一步向上跳,倍增一次跳
步!
因此若是
和
向上跳
步到達的節點相同,那咱們就不跳,讓
和
的第
祖先不一樣便可跳
注意每下一次向上跳的距離是上一次跳的
(
),直到
,
即已被求出
這樣使 和 與 的距離每次縮短一半,時間複雜度也就是 的
最後如何把
和
調至同一深度?
實際上是同樣的,先把較深的那個點調淺就好了
function lca(x,y:longint):longint; var k:longint; begin if depth[x]<depth[y] then swap(x,y); k:=trunc(ln(depth[x]-depth[y]+1)/ln(2)); while k>=0 do begin if depth[anc[x,k]]>depth[y] then x:=anc[x,k]; dec(k); end; if depth[x]<>depth[y] then x:=anc[x,0]; k:=trunc(ln(d[x])/ln(2)); while k>=0 do begin if anc[x,k]<>anc[y,k] then begin x:=anc[x,k]; y:=anc[y,k]; end; dec(k); end; exit(x); end;
以上三段
已經能解決樹上倍增求
了
樹上倍增求
的時間複雜度爲
比賽裏面直接求
是沒有什麼用處的
其實,樹上倍增的真正可怕之處是這個倍增的思想
給你一棵樹和兩個點 和 ,求這兩點間路徑的路徑最大/小的點權/邊權
明顯咱們要先求
和
的
,由於惟一路徑必須通過
求
好比說用
,而後呢?暴力
再遍歷一次路徑?
不可能的
這種問題用樹上倍增仍是能用 的時間解決
咱們能夠設 表示i到他的第 個祖先的路徑最大值,就能夠邊求 邊順便求出兩點距離
由於
也符合
還有求兩點的路徑上路徑權值和呢?
這纔是樹上倍增的真正意義所在,像
求
,是沒法解決這類問題的
至此樹上倍增講的差很少了,看例題
Description 給你N個點的無向連通圖,圖中有M條邊,第j條邊的長度爲: d_j. 如今有 K個詢問。 每一個詢問的格式是:A
B,表示詢問從A點走到B點的全部路徑中,最長的邊最小值是多少?Input
文件名爲heatwave.in 第一行: N, M, K。 第2…M+1行: 三個正整數:X, Y, and D (1 <=
X <=N; 1 <= Y <= N). 表示X與Y之間有一條長度爲D的邊。 第M+2…M+K+1行: 每行兩個整數A
B,表示詢問從A點走到B點的全部路徑中,最長的邊最小值是多少?Output
對每一個詢問,輸出最長的邊最小值是多少。
Sample Input
6 6 8 1 2 5 2 3 4 3 4 3 1 4 8 2 5 7 4 6 2 1 2 1 3 1 4 2 3 2 4 5 1 6 2
6 1Sample Output
5 5 5 4 4 7 4 5
Data Constraint
50% 1<=N,M<=3000 其中30% K<=5000 100% 1 <= N <= 15,000 1 <= M <=
30,000 1 <= d_j <= 1,000,000,000 1 <= K <= 20,000Hint
先不考慮其餘的,先考慮一下如何搞定題目所給的問題
注意這句話——
從A點走到B點的全部路徑中,最長的邊的最小值是多少?
想到什麼了嗎?
這樣就能夠保證兩點間最長邊最短 ,這裏直接用kruskal+並查集艹過去就好了
用鄰接表存下便可
求完
後,這道題是否是就是道裸題?維護
和
便可
照樣用一個
遍歷整棵樹,維護出
、
和
,以後
預處理
搞定以後,咱們對每一對
求
(設
深度更深)注意求
過程當中記錄每一次的max值即
即爲答案
時間複雜度爲
我亂算的
var b,c,next,last,father,depth:array[0..50000]of longint; anc,dis:array[0..15000,0..16]of longint; a:array[0..30000,0..3]of longint; n,m,q,i,j,x,y,tot:longint; procedure swap(var x,y:longint); var z:longint; begin z:=x; x:=y; y:=z; end; function max(x,y:longint):longint; begin if x>y then exit(x); exit(y); end; procedure qsort(l,r:longint); var i,j,mid:longint; begin i:=l; j:=r; mid:=a[(l+r)div 2,3]; repeat while a[i,3]<mid do inc(i); while a[j,3]>mid do dec(j); if i<=j then begin a[0]:=a[i]; a[i]:=a[j]; a[j]:=a[0]; inc(i); dec(j); end; until i>j; if l<j then qsort(l,j); if i<r then qsort(i,r); end; function getfather(x:longint):longint; begin if father[x]=x then exit(x); father[x]:=getfather(father[x]); exit(father[x]); end; function judge(x,y:longint):boolean; begin exit(getfather(x)=getfather(y)); end; procedure insert(x,y,z:longint); begin inc(tot); b[tot]:=y; next[tot]:=last[x]; last[x]:=tot; c[tot]:=z; end; procedure dfs(x,y:longint); var now:longint; begin now:=last[x]; while now<>0 do begin if b[now]<>y then begin anc[b[now],0]:=x; depth[b[now]]:=depth[x]+1; dis[b[now],0]:=c[now]; dfs(b[now],x); end; now:=next[now]; end; end; function lca(x,y:longint):longint; var k:longint; begin lca:=0; if depth[x]<depth[y] then swap(x,y); k:=trunc(ln(depth[x]-depth[y]+1)/ln(2)); while k>=0 do begin if depth[anc[x,k]]>depth[y] then begin lca:=max(lca,dis[x,k]); x:=anc[x,k]; end; dec(k); end; if depth[x]<>depth[y] then begin lca:=max(lca,dis[x,0]); x:=anc[x,0]; end; k:=trunc(ln(depth[x])/ln(2)); while k>=0 do begin if anc[x,k]<>anc[y,k] then begin lca:=max(max(lca,dis[x,k]),dis[y,k]); x:=anc[x,k]; y:=anc[y,k]; end; dec(k); end; if x=y then exit(lca); exit(max(lca,max(dis[x,0],dis[y,0]))); end; begin readln(n,m,q); for i:=1 to n do father[i]:=i; for i:=1 to m do readln(a[i,1],a[i,2],a[i,3]); qsort(1,m); for i:=1 to m do begin if (not judge(a[i,1],a[i,2])) then begin insert(a[i,1],a[i,2],a[i,3]); insert(a[i,2],a[i,1],a[i,3]); father[getfather(a[i,1])]:=getfather(a[i,2]); end; end; depth[1]:=1; dfs(1,0); for j:=1 to trunc(ln(n)/ln(2)) do for i:=1 to n do begin anc[i,j]:=anc[anc[i,j-1],j-1]; dis[i,j]:=max(dis[i,j-1],dis[anc[i,j-1],j-1]); end; for i:=1 to q do begin readln(x,y); writeln(lca(x,y)); end; end.
Description
在這個問題中,給定一個值S和一棵樹。在樹的每一個節點有一個正整數,問有多少條路徑的節點總和達到S。路徑中節點的深度必須是升序的。假設節點1是根節點,根的深度是0,它的兒子節點的深度爲1。路徑沒必要必定從根節點開始。Input
第一行是兩個整數N和S,其中N是樹的節點數。 第二行是N個正整數,第i個整數表示節點i的正整數。 接下來的N-1行每行是2個整數x和y,表示y是x的兒子。Output
輸出路徑節點總和爲S的路徑數量。Sample Input
3 3 1 2 3 1 2 1 3
Sample Output
2
Data Constraint
Hint
對於30%數據,N≤100;
對於60%數據,N≤1000;
對於100%數據,N≤100000,全部權值以及S都不超過1000。
相信這題應該難不住你了吧
正解即爲樹上倍增+二分
首先仍是同樣,用
的時間預處理
數組
至於權值,你能夠順便維護
數組,但爲什麼不用簡單的前綴和呢?
剩下來的就比較簡單啦
枚舉節點 ,二分一個 表示 的第 個祖先
而後經過 數組用 的時間求出 的第 個祖先是哪一個節點 (設爲是第 號)
若
則
,
則
就恰好找到了
此時累加答案便可
時間複雜度爲
#include<bits/stdc++.h> #define MAXN 100001 using namespace std; int last[MAXN],next[MAXN],tov[MAXN]; int a[MAXN],value[MAXN],depth[MAXN]; int f[MAXN][21]; int n,s,tot,ans; void insert(int x,int y) { next[++tot]=last[x]; last[x]=tot; tov[tot]=y; } void dfs(int x) { for (int i=last[x];i;i=next[i]) { int j=tov[i]; value[j]=value[x]+a[j]; f[j][0]=x; depth[j]=depth[x]+1; dfs(j); } } int find(int x,int k) { int t=0; while (k) { if(k&1)x=f[x][t]; t++;k/=2; } return x; } bool judge(int x) { int left=0,right=depth[x]; while (left<=right) { int mid=(left+right)/2,temp=find(x,mid); if (value[x]-value[temp]==s) { return 1; } else { if (value[x]-value[temp]>s) { right=mid-1; } else { left=mid+1; } } } return 0; } int main() { scanf("%d%d",&n,&s); for (int i=1;i<=n;i++) { scanf("%d",&a[i]); } for (int i=1;i<n;i++) { int x,y; scanf("%d%d",&x,&y); insert(x,y); } depth[1]=1,value[1]=a[1]; dfs(1); for (int j=1;j<=floor(log(n)/log(2));j++) { for (int i=1;i<=n;i++) { f[i][j]=f[f[i][j-1]][j-1]; } } for (int i=1;i<=n;i++) { if (judge(i))ans++; } printf("%d\n",ans); return 0; }
Description Z國有n座城市,n-1條雙向道路,每條雙向道路鏈接兩座城市,且任意兩座城市都能經過若干條道路相互到達。
Z國的國防部長小Z要在城市中駐紮軍隊。駐紮軍隊須要知足以下幾個條件: ①一座城市能夠駐紮一支軍隊,也能夠不駐紮軍隊。
②由道路直接鏈接的兩座城市中至少要有一座城市駐紮軍隊。 ③在城市裏駐紮軍隊會產生花費,在編號爲i的城市中駐紮軍隊的花費是pi。
小Z很快就規劃出了一種駐紮軍隊的方案,使總花費最小。可是國王又給小Z提出了m個要求,每一個要求規定了其中兩座城市是否駐紮軍隊。小Z須要針對每一個要求逐一給出回答。具體而言,若是國王提出的第j個要求可以知足上述駐紮條件(不須要考慮第j個要求以外的其它要求),則須要給出在此要求前提下駐紮軍隊的最小開銷。若是國王提出的第j個要求沒法知足,則須要輸出-1(1<=j<=m)
。如今請你來幫助小Z。Input 輸入文件名爲defense.in。 第1行包含兩個正整數n,m 和一個字符串type
,分別表示城市數、要求數和數據類型。type
是一個由大寫字母A,B或C和一個數字1,2,3組成的字符串。它能夠幫助你得到部分分。你可能不須要用到這個參數。這個參數的含義在【數據規模與約定】中有具體的描述。
第2 行n個整數pi ,表示編號i的城市中駐紮軍隊的花費。 接下來n-1行,每行兩個正整數u,v ,表示有一條u 到v 的雙向道路。 接下來
m行,第 j行四個整數a,x,b,y(a<>b)
,表示第j個要求是在城市a駐紮x支軍隊,在城市b駐紮y支軍隊。其中,x、y的取值只有0或1:若x爲0,表示城市a不得駐紮軍隊,若x爲1,表示城市a必須駐紮軍隊;若y爲0,表示城市b不得駐紮軍隊,若y爲1,表示城市b必須駐紮軍隊。
輸入文件中每一行相鄰的兩個數據之間均用一個空格分隔。Output 輸出文件名爲defense.out。 輸出共
行,每行包含1個整數,第j行表示在知足國王第j個要求時的最小開銷,若是沒法知足國王的第j個要求,則該行輸出-1。Sample Input 輸入1: 5 3 C3 2 4 1 3 9 1 5 5 2 5 3 3 4 1 0 3 0 2 1 3 1 1 0
5 0Sample Output 輸出1: 12 7
-1 【樣例解釋】 對於第一個要求,在4號和5號城市駐紮軍隊時開銷最小。 對於第二個要求,在1號、2號、3號城市駐紮軍隊時開銷最小。 第三個要求是沒法知足的,由於在1號、5號城市都不駐紮軍隊就意味着由道路直接鏈接的兩座城市中都沒有駐紮軍隊。Data Constraint
正解倍增+矩乘+樹形DP
考慮一下
的樹形
,設
表示以
爲根的子樹中,
選或不選的最優解,則
每次把某個 賦值爲 ,暴力從新 ,就能夠獲得 的好成績
接着考慮另外一個 ,設 表示以 爲根的子樹外, 選或不選的最優解
因爲咱們已經
出了
數組,咱們也能夠從一個點的父親來轉移
,則
這個 也滿好理解的,接下來是維護的問題
注意到其實沒有數據更改,那麼咱們能夠用倍增+矩乘的方法來求出一條鏈上的答案
設 表示 選或不選且 的 位祖先選或不選的最優解,則
這個應該不難理解, 和父親不能都不選,因此是
其餘的都是用父親的貢獻減去兒子的貢獻,應該不太難想
而把 數組維護出來之後,就能夠實現倍增了,用矩乘解決 的後兩維
注意倍增時的矩乘和平時的不太同樣,修改一下才能夠
對於每個詢問,若是兩個點在一條鏈上,注意一下條件選或不選的判斷就好了
不然找出兩點的 ,再倍增跳到 ,計算兩種不一樣狀況的最優解便可
用 減去所求的東西纔是最終的答案,不要忘記兩個點的 選的時候有不一樣的狀況須要取
時間複雜度
#pragma GCC optimize("O3") #pragma G++ optimize("O3") #include<stdio.h> #include<string.h> #include<algorithm> #define MAXN 100005 #define MAXM MAXN*2 #define ll long long #define reg register ll #define INF 1000000000000000007ll #define fo(i,a,b) for (reg i=a;i<=b;++i) #define fd(i,a,b) for (reg i=a;i>=b;--i) #define rep(i,a) for (reg i=last[a];i;i=next[i]) #define O3 __attribute__((optimize("-O3"))) ll last[MAXM],next[MAXM],tov[MAXM]; ll p[MAXN],depth[MAXN],fa[MAXN]; ll f[MAXN][2],g[MAXN][2]; ll anc[MAXN][21]; ll n,m,tot,ans; char type[5]; struct node { ll a[2][2]; }dis[MAXN][21],A,B,C; O3 inline ll read() { ll x=0,f=1;char ch=getchar(); while (ch<'0' || '9'<ch){if (ch=='-')f=-1;ch=getchar();} while ('0'<=ch && ch<='9')x=x*10+ch-'0',ch=getchar(); return x*f; } O3 inline ll min(ll x,ll y) { return x<y?x:y; } O3 inline void swap(ll &x,ll &y) { x=x+y,y=x-y,x=x-y; } O3 inline void link(ll x,ll y) { next[++tot]=last[x],last[x]=tot,tov[tot]=y; } O3 inline void dfs1(ll x) { depth[x]=depth[fa[x]]+1,anc[x][0]=fa[x]; fo(i,1,20)anc[x][i]=anc[anc[x][i-1]][i-1]; f[x][0]=0,f[x][1]=p[x]; rep(i,x)if (tov[i]!=fa[x]) { fa[tov[i]]=x,dfs1(tov[i]); f[x][0]+=f[tov[i]][1]; f[x][1]+=min(f[tov[i]][0],f[tov[i]][1]); } } O3 inline void dfs2(ll x) { if (x==1)g[x][0]=f[x][0],g[x][1]=f[x][1]; else { g[x][0]=f[x][0]+g[fa[x]][1]-min(f[x][0],f[x][1]); g[x][1]=f[x][1]+min(g[fa[x]][0]-f[x][1],g[fa[x]][1]-min(f[x][0],f[x][1])); } rep(i,x)if (tov[i]!=fa[x]) { dis[tov[i]][0].a[0][0]=INF; dis[tov[i]][0].a[1][0]=f[x][0]-f[tov[i]][1]; dis[tov[i]][0].a[0][1]=f[x][1]-min(f[tov[i]][0],f[tov[i]][1]); dis[tov[i]][0].a[1][1]=f[x][1]-min(f[tov[i]][0],f[tov[i]][1]); dfs2(tov[i]); } } O3 inline node merge(node A,node B) { memset(C.a,60,sizeof(C.a)); fo(i,0,1)fo(j,0,1)fo(k,0,1) C.a[i][j]=min(C.a[i][j],A.a[i][k]+B.a[k][j]); return C; } O3 int main() { freopen("defense.in","r",stdin); freopen("defense.out","w",stdout); //freopen("readin.txt","r",stdin); n=read(),m=read(),scanf("%s",&type); fo(i,1,n)p[i]=read(); fo(i,1,n-1) { ll x=read(),y=read(); link(x,y),link(y,x); } dfs1(1),dfs2(1); fo(j,1,20)fo(i,1,n)dis[i][j]=merge(dis[i][j-1],dis[anc[i][j-1]][j-1]); while (m--) { ll a=read(),x=read(),b=read(),y=read(); if (x+y==0 && ((fa[a]==b) || (fa[b]==a))) { printf("-1\n"); continue; } if (depth[a]<depth[b])swap(a,b),swap(x,y); memset(A.a,60,sizeof(A.a)); memset(B.a,60,sizeof(B.a)); A.a[x][x]=f[a][x],B.a[y][y]=f[b][y]; fd(i,20,0)if (depth[anc[a][i]]>depth[b]) { A=merge(A,dis[a][i]),a=anc[a][i]; } if (fa[a]==b) { printf("%lld\n",y==0?g[b][0]-f[a][1]+A.a[x][1]: g[b][1]-min(f[a][0],f[a][1])+min(A.a[x][0],A.a[x][1])); continue; } if (depth[a]!=depth[b])A=merge(A,dis[a][0]),a=fa[a]; fd(i,20,0)if (anc[a][i]!=anc[b][i]) { A=merge(A,dis[a][i]),B=merge(B,dis[b][i]); a=anc[a][i],b=anc[b][i]; } printf("%lld\n",min(g[fa[a]][0]-f[a][1]-f[b][1]+A.a[x][1]+B.a[y][1], g[fa[a]][1]-min(f[a][0],f[a][1])-min(f[b][0],f[b][1])+min(A.a[x][0],A.a[x][1])+min(B.a[y][0],B.a[y][1]))); } return 0; }
樹上倍增差很少算是那種必定要會的知識了
總之,這一種較爲基礎的算法,必定要熟練掌握並會運用它
(不要把正解當作暴力……)
接下來是喪心病狂 (其實比樹上倍增還簡單) 的樹鏈剖分
零樹剖基礎的人,到這裏應該仍是不懂樹剖的
簡要地說一下吧——
樹鏈剖分的思想是維護樹上路徑信息
樹鏈剖分先經過輕重邊剖分將樹分爲多條鏈,保證每一個點屬於且只屬於一條鏈
而後再經過數據結構 (樹狀數組、SBT、splay、線段樹等) 來維護每一條鏈
以上就是樹鏈剖分的思想了,不多,學完之後,其實樹剖很簡單……
問題是怎麼分輕重邊?怎麼維護?
既然樹上倍增又好打又好調,幹嗎還用樹鏈剖分?
但樹上倍增的用途還就真沒有樹鏈剖分那麼廣
不信? 再來一類看起來也很常見的題目
在一棵樹上進行路徑的權值修改,詢問路徑權值和、路徑權值極值
看起來簡單,但不簡單,線段樹、樹上倍增根本作不了
why? 後兩個操做咱們固然能夠用樹上倍增作
關鍵是修改操做,樹上倍增都預處理好了
不可能的
這時咱們就要用樹鏈剖分了
因爲咱們能夠用數據結構來維護剖好的樹,因此路徑權值之類的固然是能夠修改的
因此並非說倍增就能真正徹底通用
樹剖的用處比倍增多,倍增能作的題樹剖必定能作,反過來則否
樹剖的代碼複雜度不算特別高,調試也不難
在高級的比賽裏,樹剖是必備知識
其實樹剖也是分類別的
注意樹剖有三種:重鏈剖分、長鏈剖分和實虛鏈剖分
實虛鏈剖分其實就是LCT
因此我就分開來寫好了
正式進入樹鏈剖分的學習
請務必認認真真地閱讀如下內容不然重鏈剖分你就學不會了
設
表示以x爲根的子樹的節點個數
對於每一個非葉子節點y,y的兒子全部中
最大的兒子就是重兒子(兩個或以上都最大的話,任意選一個)
而y其它的兒子,都是輕兒子
(輕兒子一般沒有用,咱們能夠不去考慮它)
重兒子與其父親之間的路徑,就是重邊
不是重邊的邊均爲輕邊
多條重邊相連爲一條重鏈
圖中灰色的點爲重兒子,加粗的邊是重邊,其餘沒有加粗的是輕邊
這棵樹中的長度大於1的重鏈有兩條:2—5和1—3—6—10
全部輕兒子可視做一個長度爲1的重鏈
若x是輕兒子,則有
從根到某一點的路徑上,不超過 條重鏈,不超過 條輕鏈
(不須要證實是由於我不會證)
其實和着概念一塊兒看剩下三種東東,樹剖還就真挺水的連模板都不用背
接下來的數據結構維護咱們使用簡單一點的線段樹
首先用一個dfs遍歷,求出每一個點的
、
、
以及重兒子
_
其次再用一個dfs剖分,第二個
維護什麼呢?
- 以優先走重邊的原則,遍歷出來一個dfs序
- 表示x位於的重鏈上的頂端節點編號
- _ 表示x在線段樹中的位置
- 對於線段樹中的第y個位置, _ 爲它對應的dfs序中的位置
剖分完之後,每一條重鏈至關於一個區間,咱們維護處理完的數據結構便可
咱們把上面那棵樹剖分完之後線段樹中的點順序是這樣的:
1,3,6,10,8,9,7,2,5,4 (加粗的點是在重鏈上的點)
在同一條重鏈上的點,在線段樹的順序中要連在一塊兒(不然怎麼維護呢)
剖樹的時間複雜度
由於已經搞過to_tree了,經過to_tree的標號在線段樹中用單點修改便可
不要告訴我你不會單點修改
時間複雜度是線段樹修改的
詢問
到
路徑的極值(或者路徑和等等),和求
同樣邊向上跳邊記錄答案
這裏記錄的答案經過查詢數據結構獲得
關於時間複雜度嘛……
對於咱們這些蒟蒻來講最關鍵的仍是時間複雜度的問題
用最普通的數據結構線段樹來維護樹鏈剖分的時間複雜度是
後面那個 什麼東西?
因爲求
是
的,統計答案的過程當中還要查詢數據結構又是
的時間
因此每一個查詢的時間複雜度就是
了
因此你看樹剖仍是很是有用的
還有用
維護能夠作到
的時間還有LCT什麼的你如今怎麼可能會呢
樹剖固然也能求LCA啦!而且比倍增還好打耶!
具體以下
其實很好理解,跳到
的緣由是讓z進入另外一條重鏈省得程序卡住
而選擇
淺的點而不是自己淺的點是防止跳到重鏈頭超過
不懂面壁去想,倍增你都聽懂了,樹剖的你聽不懂就xxx了
那麼時間複雜度
#include<cstdio> #define MAXN 30001 using namespace std; int last[3*MAXN],next[3*MAXN],tov[3*MAXN]; int n,m,tot; struct information { int depth,father,size,heavy_son,top,to_tree,value; }a[30001]; void insert(int x,int y) { next[++tot]=last[x]; last[x]=tot; tov[tot]=y; } void dfs1(int x,int father) { int mx=-1; a[x].size=1; for (int i=last[x];i;i=next[i]) { int j=tov[i]; if (j!=father) { a[j].depth=a[x].depth+1; a[j].father=x; dfs1(j,x); a[x].size+=a[j].size; if (a[j].size>mx) { mx=a[j].size; a[x].heavy_son=j; } } } } void dfs2(int x,int father,int k) { if (x==0)return; a[x].top=k; dfs2(a[x].heavy_son,x,k); for (int i=last[x];i;i=next[i]) { int j=tov[i]; if (j!=father && j!=a[x].heavy_son && j!=0) { dfs2(j,x,j); } } } int lca(int x,int y) { while (a[x].top!=a[y].top) { if (a[a[x].top].depth>a[a[y].top].depth) x=a[a[x].top].father; else y=a[a[y].top].father; } return a[x].depth<a[y].depth?x:y; } int main() { scanf("%d",&n); for (int i=1;i<n;i++) { int x,y; scanf("%d%d",&x,&y); insert(x,y); insert(y,x); } a[1].depth=1; a[1].father=0; dfs1(1,0); dfs2(1,0,1); scanf("%d",&m); for (int i=1;i<=m;i++) { int x,y; scanf("%d%d",&x,&y); printf("%d\n",lca(x,y)); } return 0; }
仍是看例題吧
題目上面有因此就不從新貼了
我說過了倍增能作的題樹剖都能作的嘛,因此樹剖固然也能作這道題
其實在樹剖裏面這題算是模板題,並且樹鏈剖分還看得很清楚
求完了
後,咱們仍是照樣把這棵
剖分一下,用線段樹維護區間最大值
這裏咱們求的是邊權而不是點權,咱們就把邊權放到兒子節點上當作點權就能夠了
求
時咱們是一條重鏈一條重鏈地向上跳,這裏咱們是同樣的——
每次跳一條重鏈的時候,用線段樹維護好的區間最大值查詢這條重鏈的點權最大值
(還有不要告訴我你不會線段樹)
套上求 的意義也就是這樣——
- 若 ,記 和 兩點中top的深度較深的那個點爲z
- 把 跳到
最後的 即爲答案,每一個查詢是 的時間複雜度
其實想想,線段樹是把每條重鏈當作區間來維護,意義十分顯然
(注意一點就是求最大值時必定不要算上 的值)
#include<cstdio> #include<algorithm> #define MAXN 15001 #define INF 1000000007 using namespace std; int last[4*MAXN],next[4*MAXN],tov[4*MAXN],dis[4*MAXN]; int to_num[4*MAXN],father[MAXN]; int n,m,k,tot,total; struct information { int depth,father,size,heavy_son,top,to_tree,value; }a[4*MAXN]; struct point { int mx,ans; }tree[4*MAXN]; struct dist { int u,v,w; }line[4*MAXN]; bool cmp(dist a,dist b) { return a.w<b.w; } int getfather(int x) { if (father[x]==0)return x; father[x]=getfather(father[x]); return father[x]; } void insert(int x,int y,int z) { next[++tot]=last[x]; last[x]=tot; tov[tot]=y; dis[tot]=z; } void dfs1(int x,int father) { int mx=-1; a[x].size=1; for (int i=last[x];i;i=next[i]) { int j=tov[i]; if (j!=father) { a[j].depth=a[x].depth+1; a[j].father=x; a[j].value=dis[i]; dfs1(j,x); a[x].size+=a[j].size; if (a[j].size>mx) { mx=a[j].size; a[x].heavy_son=j; } } } } void dfs2(int x,int father,int top) { if (x==0)return; a[x].top=top; a[x].to_tree=++total; to_num[total]=x; dfs2(a[x].heavy_son,x,top); for (int i=last[x];i;i=next[i]) { int j=tov[i]; if (j!=father && j!=a[x].heavy_son && j!=0) { dfs2(j,x,j); } } } void maketree(int t,int l,int r) { if (l==r) { tree[t].mx=a[to_num[l]].value; return; } int mid=(l+r)/2; maketree(t*2,l,mid); maketree(t*2+1,mid+1,r); tree[t].mx=max(tree[t*2].mx,tree[t*2+1].mx); } int tree_query_max(int t,int l,int r,int x,int y) { if (l==x && r==y) { return tree[t].mx; } int mid=(l+r)/2; if (y<=mid) { return tree_query_max(t*2,l,mid,x,y); } else if (x>mid) { return tree_query_max(t*2+1,mid+1,r,x,y); } else return max(tree_query_max(t*2,l,mid,x,mid),tree_query_max(t*2+1,mid+1,r,mid+1,y)); } int query_max(int x,int y) { int temp=-1; while (a[x].top!=a[y].top) { if (a[a[x].top].depth>a[a[y].top].depth) { temp=max(temp,tree_query_max(1,1,n,a[a[x].top].to_tree,a[x].to_tree)); x=a[a[x].top].father; } else { temp=max(temp,tree_query_max(1,1,n,a[a[y].top].to_tree,a[y].to_tree)); y=a[a[y].top].father; } } if (x==y)return temp; if (a[x].depth<a[y].depth) { temp=max(temp,tree_query_max(1,1,n,a[a[x].heavy_son].to_tree,a[y].to_tree)); } else { temp=max(temp,tree_query_max(1,1,n,a[a[y].heavy_son].to_tree,a[x].to_tree)); } return temp; } int main() { //freopen("readin.txt","r",stdin); scanf("%d%d%d",&n,&m,&k); for (int i=1;i<=m;i++) { scanf("%d%d%d",&line[i].u,&line[i].v,&line[i].w); } sort(line+1,line+m+1,cmp); for (int i=1;i<=m;i++) { if (getfather(line[i].u)!=getfather(line[i].v)) { father[getfather(line[i].u)]=getfather(line[i].v); insert(line[i].u,line[i].v,line[i].w); insert(line[i].v,line[i].u,line[i].w); } } a[1].depth=1; dfs1(1,0),dfs2(1,0,1); maketree(1,1,n); for (int i=1;i<=k;i++) { int x,y; scanf("%d%d",&x,&y); printf("%d\n",query_max(x,y)); } return 0; }
必定讀懂這段代碼再看下面的題
不然會爽炸天
Description
A 國有 n 座城市,編號從 1 到 n,城市之間有 m 條雙向道路。每一條道路對車輛都有重量限制,簡稱限重。如今有 q
輛貨車在運輸貨物,司機們想知道每輛車在不超過車輛限重的狀況下,最多能運多重的貨物。Input
第一行有兩個用一個空格隔開的整數 n,m,表示 A 國有 n 座城市和 m 條道路。
接下來 m 行每行 3 個整數 x、y、z,每兩個整數之間用一個空格隔開,表示從 x 號城市到 y 號城市有一條限重爲 z 的道路。注意:x
不等於 y,兩座城市之間可能有多條道路。接下來一行有一個整數 q,表示有 q 輛貨車須要運貨。
接下來 q 行,每行兩個整數 x、y,之間用一個空格隔開,表示一輛貨車須要從 x 城市運輸貨物到 y 城市,注意:x 不等於 y。
Output
輸出共有 q 行,每行一個整數,表示對於每一輛貨車,它的最大載重是多少。若是貨車不能到達目的地,輸出-1。
Sample Input
4 3
1 2 4
2 3 3
3 1 1
3
1 3
1 4
1 3
Sample Output
3
-1
3
Data Constraint
對於 30%的數據,0 < n < 1,000,0 < m < 10,000,0 < q < 1,000;
對於 60%的數據,0 < n < 1,000,0 < m < 50,000,0 < q < 1,000;
對於 100%的數據,0 < n < 10,000,0 < m < 50,000,0 < q < 30,000,0 ≤ z ≤
100,000。
又一個套路 配樹鏈剖分
就是不想上倍增
和上面heatwave不一樣的是,這裏的原圖不必定連通,且兩城之間有多條邊
雖然說不必定連通,仍是直接上一次最大生成樹便可
多條邊就把多條邊中最大權值的邊留下,其餘的邊沒必要管
剩下線段樹維護點權最小值即和heatwave的如出一轍
但必須注意一點:判斷聯通性的問題
咱們不能只從
節點開始剖一次樹,而是必須從全部未訪問到的節點開始剖樹
至於判斷聯通性,一開始我去作洛谷的時候,是在求
途中判斷是否連通,可是WA了
其實除了
用的一次並查集外,咱們能夠再用一次並查集,把
上的邊再搞一次
須要訪問
城市時只需詢問
便可
#include<bits/stdc++.h> #define MAXN 10001 #define MAXM 50001 using namespace std; int last[2*MAXM],next[2*MAXM],tov[2*MAXM],dis[2*MAXM]; bool visited[MAXN],flag[MAXM]; int to_num[MAXN],father[MAXN],tree[4*MAXN]; int n,m,q,tot,total; struct array { int x,y,z; }dist[2*MAXM]; struct information { int depth,father,size,heavy_son,top,to_tree,value; }a[MAXN]; bool cmp(array a,array b) { return a.z>b.z; } int min(int x,int y) { return x<y?x:y; } int getfather(int x) { if (father[x]==0)return x; return father[x]=getfather(father[x]); } void insert(int x,int y,int z) { next[++tot]=last[x]; last[x]=tot; tov[tot]=y; dis[tot]=z; } void dfs1(int x,int father) { visited[x]=0; a[x].size=1; int mx=-1; for (int i=last[x];i;i=next[i]) { int j=tov[i]; if (j!=father) { if (visited[j]==0) { a[j].value+=dis[i]; continue; } a[j].depth=a[x].depth+1; a[j].value=dis[i]; a[j].father=x; dfs1(j,x); a[x].size+=a[j].size; if (a[j].size>mx) { a[x].heavy_son=j; mx=a[j].size; } } } } void dfs2(int x,int top) { if (x==0)return; a[x].top=top; a[x].to_tree=++total; to_num[total]=x; dfs2(a[x].heavy_son,top); for (int i=last[x];i;i=next[i]) { int j=tov[i]; if (j!=a[x].father && j!=a[x].heavy_son && j!=0) { dfs2(j,j); } } } void maketree(int t,int l,int r) { if (l==r) { tree[t]=a[to_num[l]].value; return; } int mid=(l+r)/2; maketree(2*t,l,mid),maketree(2*t+1,mid+1,r); tree[t]=min(tree[2*t],tree[2*t+1]); } bool judge(int x,int y) { while (a[x].top!=a[y].top) { if ((x==a[x].top && a[x].father==0)||(y==a[y].top && a[y].father==0))return 0; if (a[a[x].top].depth>a[a[y].top].depth) { x=a[a[x].top].father; } else { y=a[a[y].top].father; } } return 1; } int tree_query_min(int t,int l,int r,int x,int y) { if (l==x && y==r) { return tree[t]; } int mid=(l+r)/2; if (y<=mid) { return tree_query_min(t*2,l,mid,x,y); } else if (x>mid) { return tree_query_min(t*2+1,mid+1,r,x,y); } else { return min(tree_query_min(t*2,l,mid,x,mid),tree_query_min(t*2+1,mid+1,r,mid+1,y)); } } int query_min(int x,int y) { int temp=1000000007; while (a[x].top!=a[y].top) { if (a[a[x].top].depth>a[a[y].top].depth) { temp=min(temp,tree_query_min(1,1,n,a[a[x].top].to_tree,a[x].to_tree)); x=a[a[x].top].father; } else { temp=min(temp,tree_query_min(1,1,n,a[a[y].top].to_tree,a[y].to_tree)); y=a[a[y].top].father; } } if (x==y)return temp; if (a[x].depth<a[y].depth) { return min(temp,tree_query_min(1,1,n,a[a[x].heavy_son].to_tree,a[y].to_tree)); } else { return min(temp,tree_query_min(1,1,n,a[a[y].heavy_son].to_tree,a[x].to_tree)); } } int main() { //freopen("read.txt","r",stdin); scanf("%d%d",&n,&m); for (int i=1;i<=m;i++) { scanf("%d%d%d",&dist[i].x,&dist[i].y,&dist[i].z); } sort(dist+1,dist+m+1,cmp); for (int i=1;i<=m;i++) { int u=dist[i].x,v=dist[i].y,w=dist[i].z; if (getfather(u)!=getfather(v)) { father[getfather(u)]=v; insert(u,v,w),insert(v,u,w); flag[i]=1; } } memset(visited,1,sizeof(visited)); for (int i=1;i<=n;i++) if (visited[i]) { a[i].depth=1; a[i].father=0; dfs1(i,0),dfs2(i,i); } maketree(1,1,n); memset(father,0,sizeof(father)); for (int i=1;i<=m;i++) if (flag[i]) { int u=dist[i].x,v=dist[i].y; father[getfather(u)]=getfather(v); } scanf("%d",&q); while (q--) { int x,y; scanf("%d%d",&x,&y); if (getfather(x)==getfather(y)) { printf("%d\n",query_min(x,y)); } else printf("-1\n"); } return 0; }
Description
一棵樹上有n個節點,編號分別爲1到n,每一個節點都有一個權值w。 咱們將如下面的形式來要求你對這棵樹完成一些操做:
I. CHANGE u t : 把結點u的權值改成t II. QMAX u v: 詢問從點u到點v的路徑上的節點的最大權值
III. QSUM u v: 詢問從點u到點v的路徑上的節點的權值和 注意:從點u到點v的路徑上的節點包括u和v自己Input
輸入文件的第一行爲一個整數n,表示節點的個數。 接下來n – 1行,每行2個整數a和b,表示節點a和節點b之間有一條邊相連。
接下來n行,每行一個整數,第i行的整數wi表示節點i的權值。 接下來1行,爲一個整數q,表示操做的總數。
接下來q行,每行一個操做,以「CHANGE u t」或者「QMAX u v」或者「QSUM u v」的形式給出。Output
對於每一個「QMAX」或者「QSUM」的操做,每行輸出一個整數表示要求輸出的結果。
Sample Input
QMAX 3 4
QMAX 3 3
QMAX 3 2
QMAX 2 3
QSUM 3 4
QSUM 2 1
CHANGE 1 5
QMAX 3 4
CHANGE 3 6
QMAX 3 4
QMAX 2 4
QSUM 3 4
Sample Output
4 1 2 2 10 6 5 6 5 16
Data Constraint
Hint
【數聽說明】
對於100%的數據,保證1<=n<=30000,0<=q<=200000;中途操做中保證每一個節點的權值w在-30000到30000之間。
這題仍是樹剖經典例題
看懂
的樹剖作法,這題就比較簡單啦
這裏的線段樹要維護和查詢兩個值了,是區間最大值和區間和
仍是同樣地,求
的同時用線段樹查詢整條重鏈的區間最大值和區間和
我懶得再講兩種查詢了
至於
操做,因爲咱們已經把每一個點在線段樹中的編號(to_tree)處理出來了
因此咱們直接用線段樹的單點修改就能夠在線段樹裏面直接修改指定節點
修改完之後,推回父親節點,更新線段樹便可,change成功解決
的時間複雜度
樹剖打250行,醉了
#include<cstdio> #define MAXN 30001 #define INF 1000000007 using namespace std; int last[3*MAXN],next[3*MAXN],tov[3*MAXN]; int to_num[MAXN]; int n,m,tot,total; char st[10]; struct information { int depth,father,size,heavy_son,top,to_tree,value; }a[MAXN]; struct point { int mx,ans; }tree[MAXN*4]; int max(int x,int y) { return x>y?x:y; } void insert(int x,int y) { next[++tot]=last[x]; last[x]=tot; tov[tot]=y; } void dfs1(int x,int father) { int mx=-1; a[x].size=1; for (int i=last[x];i;i=next[i]) { int j=tov[i]; if (j!=father) { a[j].depth=a[x].depth+1; a[j].father=x; dfs1(j,x); a[x].size+=a[j].size; if (a[j].size>mx) { mx=a[j].size; a[x].heavy_son=j; } } } } void dfs2(int x,int father,int k) { if (x==0)return; a[x].top=k; a[x].to_tree=++total; to_num[total]=x; dfs2(a[x].heavy_son,x,k); for (int i=last[x];i;i=next[i]) { int j=tov[i]; if (j!=father && j!=a[x].heavy_son && j!=0) { dfs2(j,x,j); } } } void maketree(int t,int l,int r) { if (l==r) { tree[t].ans=tree[t].mx=a[to_num[l]].value; return; } int mid=(l+r)/2; maketree(t*2,l,mid); maketree(t*2+1,mid+1,r); tree[t].ans=tree[t*2].ans+tree[t*2+1].ans; tree[t].mx=max(tree[t*2].mx,tree[t*2+1].mx); } void change(int t,int l,int r,int x,int y) { if (l==r) { tree[t].ans=tree[t].mx=y; return; } int mid=(l+r)/2; if (x<=mid) { change(t*2,l,mid,x,y); } else { change(t*2+1,mid+1,r,x,y); } tree[t].mx=max(tree[t*2].mx,tree[t*2+1].mx); tree[t].ans=tree[t*2].ans+tree[t*2+1].ans; } int tree_query_max(int t,int l,int r,int x,int y) { if (l==x && r==y) { return tree[t].mx; } int mid=(l+r)/2; if (y<=mid) { return tree_query_max(t*2,l,mid,x,y); } else if (x>mid) { return tree_query_max(t*2+1,mid+1,r,x,y); } else { return max(tree_query_max(t*2,l,mid,x,mid),tree_query_max(t*2+1,mid+1,r,mid+1,y)); } } int tree_query_sum(int t,int l,int r,int x,int y) { if (l==x && r==y) { return tree[t].ans; } int mid=(l+r)/2; if (y<=mid) { return tree_query_sum(t*2,l,mid,x,y); } else if (x>mid) { return tree_query_sum(t*2+1,mid+1,r,x,y); } else { return tree_query_sum(t*2,l,mid,x,mid)+tree_query_sum(t*2+1,mid+1,r,mid+1,y); } } int query_max(int x,int y) { int temp=-INF; while (a[x].top!=a[y].top) { if (a[a[x].top].depth>a[a[y].top].depth) { temp=max(temp,tree_query_max(1,1,n,a[a[x].top].to_tree,a[x].to_tree)); x=a[a[x].top].father; } else { temp=max(temp,tree_query_max(1,1,n,a[a[y].top].to_tree,a[y].to_tree)); y=a[a[y].top].father; } } if (a[x].depth<a[y].depth) { temp=max(temp,tree_query_max(1,1,n,a[x].to_tree,a[y].to_tree)); } else { temp=max(temp,tree_query_max(1,1,n,a[y].to_tree,a[x].to_tree)); } return temp; } int query_sum(int x,int y) { int temp=0; while (a[x].top!=a[y].top) { if (a[a[x].top].depth>a[a[y].top].depth) { temp+=tree_query_sum(1,1,n,a[a[x].top].to_tree,a[x].to_tree); x=a[a[x].top].father; } else { temp+=tree_query_sum(1,1,n,a[a[y].top].to_tree,a[y].to_tree); y=a[a[y].top].father; } } if (a[x].depth<a[y].depth) { temp+=tree_query_sum(1,1,n,a[x].to_tree,a[y].to_tree); } else { temp+=tree_query_sum(1,1,n,a[y].to_tree,a[x].to_tree); } return temp; } int main() { //freopen("readin.txt","r",stdin); scanf("%d",&n); for (int i=1;i<n;i++) { int x,y; scanf("%d%d",&x,&y); insert(x,y); insert(y,x); } for (int i=1;i<=n;i++) { scanf("%d",&a[i].value); } a[1].depth=1,a[1].father=0; dfs1(1,0),dfs2(1,0,1); maketree(1,1,n); scanf("%d",&m); for (int i=1;i<=m;i++) { int x,y; scanf("%s %d %d",&st,&x,&y); if (st[1]=='M') { printf("%d\n",query_max(x,y)); } else if (st[1]=='S') { printf("%d\n",query_sum(x,y)); } else { change(1,1,n,a[x].to_tree,y); } } return 0; }
Description
給出一個N個點M條邊的無向帶權圖,以及Q個詢問,每次詢問在圖中刪掉一條邊後圖的最小生成樹。(各詢問間獨立,每次詢問不對以後的詢問產生影響,即被刪掉的邊在下一條詢問中依然存在)
Input
第一行兩個正整數N,M(N<=50000,M<=100000)表示原圖的頂點數和邊數。
下面M行,每行三個整數X,Y,W描述了圖的一條邊(X,Y),其邊權爲W(W<=10000)。保證兩點之間至多隻有一條邊。
接着一行一個正整數Q,表示詢問數。(1<=Q<=100000)
下面Q行,每行一個詢問,詢問中包含一個正整數T,表示把編號爲T的邊刪掉(邊從1到M按輸入順序編號)。Output
Q行,對於每一個詢問輸出對應最小生成樹的邊權和的值,若是圖不連通則輸出「Not connected」
Sample Input
4 4 1 2 3 1 3 5 2 3 9 2 4 1 4 1 2 3 4
Sample Output
15 13 9 Not connected
Data Constraint
Hint
數據規模: 10%的數據N,M,Q<=100。 另外30%的數據,N<=1000 100%的數據如題目。
正解仍是——MST+樹鏈剖分
奇怪的是靜態樹上問題MST+樹剖是否是萬能的…?
想想即知,若是刪去的邊不在原圖的
上,對原答案沒有影響
可是若是刪去了
上的邊,那麼其餘的
樹邊必定不變
那此時要找的只是一條非
上的邊,且要求最短,顯然是在樹鏈上
咱們在求完
後,用樹鏈剖分維護每條非MST樹邊可以使多少條MST樹邊刪去後連通
說着很拗口,但操做仍是很
的,標記永久化一下便可
固然原圖就是不連通的話,
個操做全都輸出**「Not connected」**
時間複雜度爲
#include<bits/stdc++.h> #define MAXN 50001 #define MAXM 100001 using namespace std; int last[2*MAXM],next[2*MAXM],tov[2*MAXM],dis[2*MAXM]; int father[MAXN],where[MAXM],to_num[MAXN],tree[4*MAXN],ref[2*MAXN]; int n,m,q,tot,total,ans; int min(int x,int y) { return x<y?x:y; } int read() { int x=0,f=1; char ch=getchar(); while (ch<'0' || '9'<ch) { if (ch=='-')f=-1; ch=getchar(); } while ('0'<=ch && ch<='9') { x=x*10+ch-'0'; ch=getchar(); } return x*f; } struct information { int depth,father,size,heavy_son,top,to_tree,value; }a[MAXN]; struct dist { int x,y,z,num; bool flag; }line[MAXM]; bool cmp(dist a,dist b) { return a.z<b.z; } bool cmp1(dist a,dist b) { return a.num<b.num; } int getfather(int x) { return !father[x]?x:father[x]=getfather(father[x]); } void insert(int x,int y,int z) { next[++tot]=last[x]; last[x]=tot; tov[tot]=y; dis[tot]=z; } void dfs1(int x,int father) { a[x].size=1; int mx=-1; for (int i=last[x];i;i=next[i]) { int j=tov[i]; if (j!=father && j!=0) { a[j].depth=a[x].depth+1; a[j].father=x; ref[dis[i]]=j; dfs1(j,x); a[x].size+=a[j].size; if (a[j].size>mx) { mx=a[j].size; a[x].heavy_son=j; } } } } void dfs2(int x,int top) { if (x==0)return; a[x].top=top; a[x].to_tree=++total; to_num[total]=x; dfs2(a[x].heavy_son,top); for (int i=last[x];i;i=next[i]) { int j=tov[i]; if (j!=a[x].father && j!=a[x].heavy_son && j!=0) { dfs2(j,j); } } } void change(int t,int l,int r,int x,int y,int z) { if (l==x && y==r) { tree[t]=min(tree[t],z); return; } int mid=(l+r)/2; if (y<=mid) { change(t*2,l,mid,x,y,z); } else if (x>mid) { change(t*2+1,mid+1,r,x,y,z); } else { change(t*2,l,mid,x,mid,z); change(t*2+1,mid+1,r,mid+1,y,z); } //tree[t]=min(tree[t*2],tree[t*2+1]); } int query(int t,int l,int r,int x) { if (l==r) { return tree[t]; } int mid=(l+r)/2; if (x<=mid)return min(tree[t],query(t*2,l,mid,x)); else return min(tree[t],query(t*2+1,mid+1,r,x)); } void modify(int x,int y,int z) { while (a[x].top!=a[y].top) { if (a[a[x].top].depth>a[a[y].top].depth) { change(1,1,n,a[a[x].top].to_tree,a[x].to_tree,z); x=a[a[x].top].father; } else { change(1,1,n,a[a[y].top].to_tree,a[y].to_tree,z); y=a[a[y].top].father; } } if (x==y)return; if (a[x].depth>a[y].depth) { change(1,1,n,a[a[y].heavy_son].to_tree,a[x].to_tree,z); } else { change(1,1,n,a[a[x].heavy_son].to_tree,a[y].to_tree,z); } } int main() { //freopen("read.txt","r",stdin); n=read(),m=read(); for (int i=1;i<=m;i++) { line[i].x=read(),line[i].y=read(),line[i].z=read(); line[i].num=i; } if (m<n-1) { q=read(); while (q--)printf("Not connected\n"); return 0; } sort(line+1,line+m+1,cmp); for (int i=1;i<=m;i++) { int u=line[i].x,v=line[i].y,w=line[i].z; if (getfather(u)!=getfather(v)) { father[getfather(u)]=v; line[i].flag=1; ans+=w; insert(u,v,line[i].num); insert(v,u,line[i].num); } } a[1].depth=1; a[1].father=0; dfs1(1,0),dfs2(1,1); memset(tree,0x3f,sizeof(tree)); sort(line+1,line+m+1,cmp1); for (int i=1;i<=m;i++) { if (!line[i].flag) { modify(line[i].x,line[i].y,line[i].z); } } q=read(); while (q--) { int x=read(); if (!line[x].flag) { printf("%d\n",ans); } else { int xx=query(1,1,n,a[ref[x]].to_tree); if(xx==0x3f3f3f3f) { printf("Not connected\n"); } else { printf("%d\n",ans-line[x].z+xx); } } } return 0; }
最近作模擬賽,看到樹上問題就開碼樹剖 (我變成無腦xxxx玄手了麼)
可是樹剖對付靜態樹問題基本都能完美解決
樹剖用處確實很是很是多,不只要熟練運用,還要作到舉一反三、用老知識解決新題目
我不是樹剖講師因此樹剖學的仍是很差
動態樹問題呢?樹剖GG,祭出
吧
(LCT明明比樹剖高級多了)
靜態樹問題在前面已經見識過了
就是樹的形態不發生變化,只是樹上的權值變化,用數據結構隨便維護一下的問題罷了
可是樹的形態若是會變化呢?(例如添、刪樹邊操做等)
這就是 動態樹問題(Dynamic Tree Problem) 了
楊哲的《QTREE 解法的一些研究》 你應該據說過
因爲百度像csdn同樣要XX幣才能下載那些文檔,就百度雲共享一波啦
這裏下載,uuq0
LCT(Link-Cut Tree),解決動態樹問題的一種算法
LCT≈splay+樹剖,是解決動態樹問題的一把利器
核心思想就是借鑑樹剖的 輕重邊 等概念,用比線段樹靈活的
來維護一棵樹(森林)
意會就好
(但真相是tarjan老爺子是在發明LCT以後才發明的splay)
動態樹問題一般要求你對一棵樹進行切割和拼接等操做,而後再在上面維護傳統數據結構可維護的值
樹剖作不了的軟肋在於重兒子和重鏈早就剖分(預處理)好了
怎麼添、刪邊?暴力重構全部重兒子和整個線段樹?
100%GG
不用
也能勉強作某些動態樹問題了 (注意某些)
若是只有添邊或只有刪邊且不要求強制在線的題目,用離線+樹剖+splay/動態開點線段樹是能夠作的
雖然說樹剖勉強支持刪邊操做,但這畢竟不是最通用、最完美的作法
什麼東西能真正完美地解決這一類動態樹問題呢?
Link-Cut Tree算法
並且 能作的除了動態樹問題還有
任何樹剖題
可支持刪邊的並查集
可作不用排序的 (動態加邊的 並查集)
等……
我以前不是說過樹上倍增能作的題樹剖必定能作嗎
而樹鏈剖分能作的題
也必定能作
LCA呢?
LCT是不能夠求動態樹上的LCA的……
爲何不能求?這個等下再說吧
這就是一個LCT
維護的其實算是一個連通性森林
- Preferred Child:重兒子,重兒子與父親節點在同一棵splay中,一個節點至多一個重兒子
- Preferred Edge:重邊,鏈接父親節點和重兒子的邊
- Preferred Path :重鏈,由重邊及重邊鏈接的節點構成的鏈
你看這個明顯和樹剖的東東一個鬼樣因此能夠略過
原來的那些名稱都叫什麼「偏好」XX的,感受用樹剖名詞就能夠了
接下來是 裏一個獨♂特的東西
- Auxiliary Tree:輔助樹
- 由一條重鏈上的全部節點所構成的Splay稱做這條鏈的輔助樹
- 每一個點的鍵值爲這個點的深度,即這棵Splay的中序遍歷是這條鏈從鏈頂到鏈底的全部節點構成的序列
- 輔助樹的根節點的父親指向鏈頂的父親節點,然而鏈頂的父親節點的兒子並不指向輔助樹的根節點
- (也就是說父親不認輕兒子只認重兒子,兒子都認父親)
- 這條性質爲後來的操做提供了依據
上面框框裏面是上網copy的,不知道怎麼解釋
必須注意輔助樹(splay)的根≠原樹的根
LCT不是樹!!!是個森林OK?LCA都求不了這叫作樹麼?
在同一棵splay裏面固然能夠暴力求LCA,但這有意義麼?
其次,爲何LCT裏的點不認輕重兒子?
在一個splay裏的點不就是父親和重兒子和重兒子的重兒子……麼?
對上面的定義簡單點說,在同一條「重鏈」裏的全部點在一個splay中
不懂就看上面那個
本身理解,如
與
和
是在一個splay裏的
這個明明很簡單
splay過程當中,樹的形態能夠變化如
可是原樹是不會變的
LCT還有個特色,就是不用記錄節點編號
由於一個點的右兒子的深度是比它的大的,不像splay同樣須要查找什麼的:D
其實更值得一提的是上面提到的虛邊(也就是對應樹剖的輕邊)的那個東西
虛邊
儲存在一個splay的根節點,
爲
在LCT裏的父親(即在不在當前輔助樹裏的那個)
而
則是splay中
的父親,不要把這個搞混
例子:圖中
而
,
而
一樣本身去看圖理解懶得多說
能夠這樣想:兩個重鏈是用一條虛邊鏈接的,沒有虛邊不就成一條重鏈了麼?
看完這些,其實LCT仍是很簡單的……
那麼,不看懂概念怎麼能夠去學LCT的操做呢?
請務必認認真真地閱讀如下內容不然LCT你就學不會了
一句話說了N遍
access操做是LCT裏最基礎、最重要的操做
定義 操做就是把 的重兒子與 斷開,而後把從整個LCT的根到 之間的路徑都變爲重邊
access操做實際上是很簡單的
若如今要 節點 ,每一次把 旋轉到當前輔助樹的根
此時 的右兒子深度比 大,即右兒子是重兒子,咱們天然而然的就要把它給刪掉
把 連上 所在的輔助樹 (splay) 裏,而後
簡單點說就是把 的右兒子做爲輔助樹的新根,並連一條向 的虛邊
重複以上操做,直到
這個不簡單嗎?
tarjan老爺子說, 一次的時間複雜度是
整個 要進行幾回旋轉操做呢?
把若干個小輔助樹合併爲一個大輔助樹,只進行一次 操做
因爲
均攤
,又由於每次只對一個大的輔助樹
,咱們能夠大膽想象得出只有
的時間
因此 操做的均攤時間是
之後任何對access操做時間複雜度有疑問的請去找tarjan老爺子喝茶
問我沒用
cc人生贏家的LCT好漂亮啊
void down(int x) { if (a[x].rev) { reverse(t[x][0]),reverse(t[x][1]); a[x].rev=0; } } void update(int x) { if(x) { a[x].size=a[t[x][0]].size+a[t[x][1]].size+1; } } void downdata(int x) { while (x)st[++st[0]]=x,x=fa[x]; while (st[0])down(st[st[0]--]); } //從x到輔助樹的根的整條路徑上的邊都要下傳翻轉標記 //……其實也就是暴力下傳標記 int lr(int x) { return t[fa[x]][1]==x; } void rotate(int x) { int y=fa[x],k=lr(x); t[y][k]=t[x][!k]; if(t[x][!k])fa[t[x][!k]]=y; fa[x]=fa[y]; if(fa[y])t[fa[y]][lr(y)]=x; t[x][!k]=y; fa[y]=x; pf[x]=pf[y]; update(y),update(x); } void splay(int x,int y) { //downdata(x); while(fa[x]!=y) { if(fa[fa[x]]!=y) { if(lr(x)==lr(fa[x]))rotate(fa[x]); else rotate(x); } rotate(x); } } void access(int x) { for (int y=0;x;update(x),y=x,x=pf[x]) { splay(x,0); fa[t[x][1]]=0; pf[t[x][1]]=x; t[x][1]=y; fa[y]=x; pf[y]=0; } }
makeroot操做和access操做是LCT的最核心的操做
其餘操做視題而定,但makeroot和access必不可少
定義 操做就是將 所在的splay翻轉,使 變爲原LCT樹的根
大概這樣吧……
不算 的話 只有splay一次的時間複雜度爲
makeroot不就更簡單了?
首先 後,此時的 是輔助樹中深度最大的點,接着把 旋轉(splay)到LCT的根
而此時在輔助樹中,除了 的其餘點都是其父親的左兒子
而後再splay區間翻轉便可,打上lazy-tag標記
而後 才成功解決,如上圖的那種樣子
void swap(int &x,int &y) { int z=x; x=y; y=z; } void reverse(int x) { if(x) { a[x].rev^=1; swap(t[x][0],t[x][1]); } } void down(int x) { if (a[x].rev) { reverse(t[x][0]),reverse(t[x][1]); a[x].rev=0; } } void makeroot(int x) { access(x); splay(x,0); reverse(x); }
link和cut嘛……因此LCT要叫Link-Cut Tree啊
定義 操做就是將節點 連到節點 的兒子
link太簡單了不想放圖
link操做就太簡單了
後,此時 是原LCT的根
此時 沒有 ,而從 連向 會沖掉原來 的
因此從 向 連一條虛邊,即
void link(int x,int y) { makeroot(x); pf[x]=y; }
定義 操做就是將節點 (x是y的兒子)與父親 斷開
cut太簡單了也不想放圖
cut和link其實很相似
且 後, 是原樹的根,且 和 在同一splay裏
而後 ,保證 是根,又因爲 連通,因此此時 是 的右兒子
因此 就行了
void cut(int x,int y) { makeroot(x); access(y); splay(x,0); t[x][1]=fa[y]=pf[y]=0; update(x); }
定義 爲 節點當前輔助樹的根
退化版並查集路徑壓縮
小學生級別難度
暴力把 向上跳,直到
沒了
int getroot(int x) { while (fa[x])x=fa[x]; return x; }
定義 ,若 聯通則爲 ,不然爲
退化版並查集集合查詢
比 稍微難一丁點
後, 爲原樹根, 打通到了最頂上的點(不必定是原樹根)
若 間聯通,則
因此詢問 便可
bool judge(int x,int y) { makeroot(x); access(y); splay(x,0); return getroot(y)==x; }
和樹剖的差很少,
的題目仍是問你
點到
點間的路徑路徑和或者極值什麼的
樹剖很好作靜態樹的嘛
LCT怎麼解決這類問題呢?
其實有常規套路
好比詢問 到 間路徑和
後,再 ,此時 和 就在同一棵splay裏了
而後這條路徑不就職你擺佈了?
不考慮LCT求LCA的話會發現
頗有某些優點
代碼複雜度的排序,應該是倍增優、LCT稍劣一點、樹剖不好
功能的排序固然是LCT最多、樹剖少、倍增最少
忽然發現LCT很好打、很好用?
因爲splay帶來的巨大常數因子, 的時間大約是樹剖的幾倍……
好比這個
這是【ZJOI2008】樹的統計
上面是樹剖下面LCT,比對一下代碼長度和時間
因此不要聽tarjan那傢伙BB說LCT時間很好
並且還有一個硬傷是求不了LCA
靜態樹上的兩個點,LCA是惟一肯定的,維護的值怎麼變都沒有影響LCA
splay的LCA是肯定的麼……
因此比賽裏有動態樹求LCA就趕快棄掉
因爲 使用靈活的 維護數據,維護點權易如反掌
怎麼維護LCT的邊權呢?
可能先想到的是採起樹剖思想,邊權當作兒子的點權
不行
因爲
的
旋轉,兒子旋轉後就成爲了父親
那維護的權值不都亂套了?
解決方法很簡單
把一條鏈接
的邊當作一個點,再把這個點和
分別連起來
如此就能夠維護LCT的邊權了
必須注意要開兩倍空間!
事實證實LCT很是好用
因此須要海量例題來讓你刻骨銘心
題目描述
輝輝熱衷於洞穴勘測。
某天,他按照地圖來到了一片被標記爲JSZX的洞穴羣地區。通過初步勘測,輝輝發現這片區域由n個洞穴(分別編號爲1到n)以及若干通道組成,而且每條通道鏈接了剛好兩個洞穴。假如兩個洞穴能夠經過一條或者多條通道按必定順序鏈接起來,那麼這兩個洞穴就是連通的,按順序鏈接在一塊兒的這些通道則被稱之爲這兩個洞穴之間的一條路徑。
洞穴都十分堅固沒法破壞,然而通道不太穩定,時常由於外界影響而發生改變,好比,根據有關儀器的監測結果,123號洞穴和127號洞穴之間有時會出現一條通道,有時這條通道又會由於某種稀奇古怪的緣由被毀。輝輝有一臺監測儀器能夠實時將通道的每一次改變情況在輝輝手邊的終端機上顯示:
若是監測到洞穴u和洞穴v之間出現了一條通道,終端機上會顯示一條指令 Connect u v
若是監測到洞穴u和洞穴v之間的通道被毀,終端機上會顯示一條指令 Destroy u v
通過長期的堅苦卓絕的手工推算,輝輝發現一個奇怪的現象:不管通道怎麼改變,任意時刻任意兩個洞穴之間至多隻有一條路徑。
於是,輝輝堅信這是因爲某種本質規律的支配致使的。於是,輝輝更加夜以繼日地堅守在終端機以前,試圖經過通道的改變狀況來研究這條本質規律。
然而,終於有一天,輝輝在堆積成山的演算紙中崩潰了……他把終端機往地面一砸(終端機也足夠堅固沒法破壞),轉而求助於你,說道:「你老兄把這程序寫寫吧」。輝輝但願能隨時經過終端機發出指令 Query u v,向監測儀詢問此時洞穴u和洞穴v是否連通。如今你要爲他編寫程序回答每一次詢問。
已知在第一條指令顯示以前,JSZX洞穴羣中沒有任何通道存在。輸入輸出格式
輸入格式: 第一行爲兩個正整數n和m,分別表示洞穴的個數和終端機上出現過的指令的個數。
如下m行,依次表示終端機上出現的各條指令。每行開頭是一個表示指令種類的字符串s("Connect」、」Destroy」或者」Query」,區分大小寫),以後有兩個整數u和v
(1≤u, v≤n且u≠v) 分別表示兩個洞穴的編號。輸出格式: 對每一個Query指令,輸出洞穴u和洞穴v是否互相連通:是輸出」Yes」,不然輸出」No」。(不含雙引號)
輸入輸出樣例
輸入樣例#1: 複製 200 5 Query 123 127 Connect 123 127 Query 123 127 Destroy
127 123 Query 123 127 輸出樣例#1: 複製 No Yes No 輸入樣例#2: 複製 3 5 Connect 1 2
Connect 3 1 Query 2 3 Destroy 1 3 Query 2 3 輸出樣例#2: 複製 Yes No 說明數聽說明
10%的數據知足n≤1000, m≤20000
20%的數據知足n≤2000, m≤40000
30%的數據知足n≤3000, m≤60000
40%的數據知足n≤4000, m≤80000
50%的數據知足n≤5000, m≤100000
60%的數據知足n≤6000, m≤120000
70%的數據知足n≤7000, m≤140000
80%的數據知足n≤8000, m≤160000
90%的數據知足n≤9000, m≤180000
100%的數據知足n≤10000, m≤200000
保證全部Destroy指令將摧毀的是一條存在的通道
本題輸入、輸出規模比較大,建議c\c++選手使用scanf和printf進行I\O操做以避免超時
……智障題
這題是最普通的link、cut操做和判斷聯通
後,判斷 便可
不要告訴我說不知道怎麼link和cut
#include<bits/stdc++.h> #define MAXN 30001 using namespace std; int t[MAXN][2]; int b[MAXN],fa[MAXN],pf[MAXN],st[MAXN]; char s[10]; int n,m; struct node { int val,sum,mx,size; bool rev; }a[MAXN]; int read() { int x=0,f=1; char ch=getchar(); while (ch<'0' || '9'<ch) { if (ch=='-')f=-1; ch=getchar(); } while ('0'<=ch && ch<='9') { x=x*10+ch-'0'; ch=getchar(); } return x*f; } void reverse(int x) { if(x) { a[x].rev^=1; swap(t[x][0],t[x][1]); } } void down(int x) { if (a[x].rev) { reverse(t[x][0]),reverse(t[x][1]); a[x].rev=0; } } void update(int x) { if (x) { a[x].size=a[t[x][0]].size+a[t[x][1]].size+1; } } void downdata(int x) { st[0]=0; while (x)st[++st[0]]=x,x=fa[x]; while (st[0])down(st[st[0]--]); } int lr(int x) { return t[fa[x]][1]==x; } void rotate(int x) { int y=fa[x],k=lr(x); t[y][k]=t[x][!k]; if (t[x][!k])fa[t[x][!k]]=y; fa[x]=fa[y]; if (fa[y])t[fa[y]][lr(y)]=x; t[x][!k]=y; fa[y]=x,pf[x]=pf[y]; update(y),update(x); } void splay(int x, int y) { downdata(x); while (fa[x]!=y) { if (fa[fa[x]]!=y) { if (lr(x)==lr(fa[x]))rotate(fa[x]); else rotate(x); } rotate(x); } } void access(int x) { for (int y=0;x;update(x),y=x,x=pf[x]) { splay(x,0); fa[t[x][1]]=0; pf[t[x][1]]=x; t[x][1]=y; fa[y]=x; pf[y]=0; } } void makeroot(int x) { access(x); splay(x,0); reverse(x); } void link(int x,int y) { makeroot(x); pf[x]=y; } void cut(int x,int y) { makeroot(x); access(y); splay(x,0); t[x][1]=fa[y]=pf[y]=0; update(x); } int getroot(int x) { while (fa[x])x=fa[x]; return x; } bool judge(int x,int y) { makeroot(x); access(y); splay(x,0); return getroot(y)==x; } int main() { n=read(),m=read(); while (m--) { scanf("%s",&s); int x=read(),y=read(); if (s[0]=='C')link(x,y); else if (s[0]=='D')cut(x,y); else (judge(x,y))?(printf("Yes\n")):(printf("No\n")); } }
題目描述
某天,Lostmonkey發明了一種超級彈力裝置,爲了在他的綿羊朋友面前顯擺,他邀請小綿羊一塊兒玩個遊戲。遊戲一開始,Lostmonkey在地上沿着一條直線擺上n個裝置,每一個裝置設定初始彈力系數ki,當綿羊達到第i個裝置時,它會日後彈ki步,達到第i+ki個裝置,若不存在第i+ki個裝置,則綿羊被彈飛。綿羊想知道當它從第i個裝置起步時,被彈幾回後會被彈飛。爲了使得遊戲更有趣,Lostmonkey能夠修改某個彈力裝置的彈力系數,任什麼時候候彈力系數均爲正整數。
輸入輸出格式
輸入格式: 第一行包含一個整數n,表示地上有n個裝置,裝置的編號從0到n-1。
接下來一行有n個正整數,依次爲那n個裝置的初始彈力系數。
第三行有一個正整數m,
接下來m行每行至少有兩個數i、j,若i=1,你要輸出從j出發被彈幾回後被彈飛,若i=2則還會再輸入一個正整數k,表示第j個彈力裝置的係數被修改爲k。
輸出格式: 對於每一個i=1的狀況,你都要輸出一個須要的步數,佔一行。
輸入輸出樣例
輸入樣例#1: 複製 4 1 2 1 1 3 1 1 2 1 1 1 1 輸出樣例#1: 複製 2 3 說明
對於20%的數據n,m<=10000,對於100%的數據n<=200000,m<=100000
LCT板子題
咱們能夠建一個 號節點, 號節點連到表示 號節點就表示被彈飛
若要更改,直接先 再 一波,關鍵怎麼詢問彈飛幾回呢?暴力splay?
咱們能夠 ,使 號節點變爲原LCT的根
接着咱們 後 ,也就是把 旋轉到原樹的根
那麼 號節點會彈 次被彈飛,可是爲何呢?
因爲輔助樹按照深度關鍵字排序,因此x的子樹大小-1就是要被彈飛幾回了
因此明顯正確
#include<stdio.h> #include<string.h> #define MAXN 200001 using namespace std; int t[MAXN][2]; int b[MAXN],fa[MAXN],pf[MAXN],st[MAXN]; int n,m; struct node { int size,rev; }a[MAXN]; int min(int x,int y) { return x<y?x:y; } void swap(int &x,int &y) { int z=x; x=y; y=z; } void reverse(int x) { if(x) { a[x].rev^=1; swap(t[x][0],t[x][1]); } } void down(int x) { if (a[x].rev) { reverse(t[x][0]),reverse(t[x][1]); a[x].rev=0; } } void update(int x) { if(x) { a[x].size=a[t[x][0]].size+a[t[x][1]].size+1; } } void downdata(int x) { while (x)st[++st[0]]=x,x=fa[x]; while (st[0])down(st[st[0]--]); } int lr(int x) { return t[fa[x]][1]==x; } void rotate(int x) { int y=fa[x],k=lr(x); t[y][k]=t[x][!k]; if(t[x][!k])fa[t[x][!k]]=y; fa[x]=fa[y]; if(fa[y])t[fa[y]][lr(y)]=x; t[x][!k]=y; fa[y]=x; pf[x]=pf[y]; update(y),update(x); } void splay(int x, int y) { downdata(x); while(fa[x]!=y) { if(fa[fa[x]]!=y) { if(lr(x)==lr(fa[x]))rotate(fa[x]); else rotate(x); } rotate(x); } } void access(int x) { for (int y=0;x;update(x),y=x,x=pf[x]) { splay(x,0); fa[t[x][1]]=0; pf[t[x][1]]=x; t[x][1]=y; fa[y]=x; pf[y]=0; } } void makeroot(int x) { access(x); splay(x,0); reverse(x); } void link(int x,int y) { makeroot(x); pf[x]=y; } void cut(int x,int y) { makeroot(x); access(y); splay(x,0); t[x][1]=fa[y]=pf[y]=0; update(x); } int main() { scanf("%d",&n); for (int i=1;i<=n;i++) { scanf("%d",&b[i]); link(i,min(n+1,i+b[i])); } scanf("%d",&m); while (m--) { int z,x; scanf("%d%d",&z,&x),x++; if (z==1) { makeroot(n+1); access(x); splay(x,0); printf("%d\n",a[x].size-1); } else { cut(x,min(x+b[x],n+1)); scanf("%d",&b[x]); link(x,min(x+b[x],n+1)); } } }
題目上面有因此就不從新貼了
前面是用樹剖作的,如今用LCT重作了一次
其實LCT的代碼複雜度簡直秒殺樹剖好伐
都說了LCT能作全部樹剖題,然而這題連cut操做都沒有
那不就簡單了?
像普通套路同樣,詢問 間的路徑極值或權值和,就
而後直接輸出 就行了
至於修改 節點的操做,把 旋到根,直接修改便可
然而WA90
爲何呢?
每次輸入的時候,都要把當前節點旋到根,讀入後再
這樣才終於切了
#include<bits/stdc++.h> #define MAXN 30001 using namespace std; int t[MAXN][2]; int b[MAXN],fa[MAXN],pf[MAXN],st[MAXN]; char s[10]; int n,m; struct node { int val,sum,mx,size,rev; }a[MAXN]; int read() { int x=0,f=1; char ch=getchar(); while (ch<'0' || '9'<ch) { if (ch=='-')f=-1; ch=getchar(); } while ('0'<=ch && ch<='9') { x=x*10+ch-'0'; ch=getchar(); } return x*f; } void reverse(int x) { if(x) { a[x].rev^=1; swap(t[x][0],t[x][1]); } } void down(int x) { if (a[x].rev) { reverse(t[x][0]),reverse(t[x][1]); a[x].rev=0; } } void update(int x) { if (x) { a[x].size=a[t[x][0]].size+a[t[x][1]].size+1; a[x].sum=a[x].val+a[t[x][0]].sum+a[t[x][1]].sum; a[x].mx=max(a[x].val,max(a[t[x][0]].mx,a[t[x][1]].mx)); } } void downdata(int x) { st[0]=0; while (x)st[++st[0]]=x,x=fa[x]; while (st[0])down(st[st[0]--]); } int lr(int x) { return t[fa[x]][1]==x; } void rotate(int x) { int y=fa[x],k=lr(x); t[y][k]=t[x][!k]; if (t[x][!k])fa[t[x][!k]]=y; fa[x]=fa[y]; if (fa[y])t[fa[y]][lr(y)]=x; t[x][!k]=y; fa[y]=x,pf[x]=pf[y]; update(y),update(x); } void splay(int x, int y) { downdata(x); while (fa[x]!=y) { if (fa[fa[x]]!=y) { if (lr(x)==lr(fa[x]))rotate(fa[x]); else rotate(x); } rotate(x); } } void access(int x) { for (int y=0;x;update(x),y=x,x=pf[x]) { splay(x,0); fa[t[x][1]]=0; pf[t[x][1]]=x; t[x][1]=y; fa[y]=x; pf[y]=0; } } void makeroot(int x) { access(x); splay(x,0); reverse(x); } void link(int x,int y) { makeroot(x); pf[x]=y; } int main() { n=read(); for (int i=1;i<n;i++)link(read(),read()); a[0].mx=-2147483647,a[0].sum=a[0].val=0; for (int i=1;i<=n;i++) { splay(i,0); a[i].mx=a[i].sum=a[i].val=read(); update(i); } m=read(); while (m--) { scanf("%s",&s); int x=read(),y=read(); if (s[1]=='M')//max { makeroot(y); access(x); splay(x,0); printf("%d\n",a[x].mx); } else if (s[1]=='S')//sum { makeroot(y); access(x); splay(x,0); printf("%d\n",a[x].sum); } else //change { splay(x,0); a[x].val=y; update(x); } } }
題目背景
動態樹
題目描述
給定n個點以及每一個點的權值,要你處理接下來的m個操做。操做有4種。操做從0到3編號。點從1到n編號。
0:後接兩個整數(x,y),表明詢問從x到y的路徑上的點的權值的xor和。保證x到y是聯通的。
1:後接兩個整數(x,y),表明鏈接x到y,若x到y已經聯通則無需鏈接。
2:後接兩個整數(x,y),表明刪除邊(x,y),不保證邊(x,y)存在。
3:後接兩個整數(x,y),表明將點x上的權值變成y。
輸入輸出格式
輸入格式: 第1行兩個整數,分別爲n和m,表明點數和操做數。
第2行到第n+1行,每行一個整數,整數在[1,10^9]內,表明每一個點的權值。
第n+2行到第n+m+1行,每行三個整數,分別表明操做類型和操做所需的量。
輸出格式: 對於每個0號操做,你須輸出x到y的路徑上點權的xor和。
輸入輸出樣例
輸入樣例#1: 複製 3 3 1 2 3 1 1 2 0 1 2 0 1 1 輸出樣例#1: 複製 3 1 說明
數據範圍: 1 \leq N, M \leq 3 \cdot {10}^5 1≤N,M≤3⋅10 5
仍是LCT板子題差很少是板子在手天下我有
注意兩點就好
的兩個點不必定聯通,用 特判一下
還有就是異或和的話
如此簡單
#include<bits/stdc++.h> #define MAXN 300001 using namespace std; int t[MAXN][2]; int b[MAXN],fa[MAXN],pf[MAXN],st[MAXN]; char s[10]; int n,m; struct node { int val,sum,mx,size; bool rev; }a[MAXN]; int read() { int x=0,f=1; char ch=getchar(); while (ch<'0' || '9'<ch) { if (ch=='-')f=-1; ch=getchar(); } while ('0'<=ch && ch<='9') { x=x*10+ch-'0'; ch=getchar(); } return x*f; } void reverse(int x) { if(x) { a[x].rev^=1; swap(t[x][0],t[x][1]); } } void down(int x) { if (a[x].rev) { reverse(t[x][0]),reverse(t[x][1]); a[x].rev=0; } } void update(int x) { if (x) { a[x].size=a[t[x][0]].size+a[t[x][1]].size+1; a[x].sum=a[x].val^a[t[x][0]].sum^a[t[x][1]].sum; } } void downdata(int x) { st[0]=0; while (x)st[++st[0]]=x,x=fa[x]; while (st[0])down(st[st[0]--]); } int lr(int x) { return t[fa[x]][1]==x; } void rotate(int x) { int y=fa[x],k=lr(x); t[y][k]=t[x][!k]; if (t[x][!k])fa[t[x][!k]]=y; fa[x]=fa[y]; if (fa[y])t[fa[y]][lr(y)]=x; t[x][!k]=y; fa[y]=x,pf[x]=pf[y]; update(y),update(x); } void splay(int x, int y) { downdata(x); while (fa[x]!=y) { if (fa[fa[x]]!=y) { if (lr(x)==lr(fa[x]))rotate(fa[x]); else rotate(x); } rotate(x); } } void access(int x) { for (int y=0;x;update(x),y=x,x=pf[x]) { splay(x,0); fa[t[x][1]]=0; pf[t[x][1]]=x; t[x][1]=y; fa[y]=x; pf[y]=0; } } void makeroot(int x) { access(x); splay(x,0); reverse(x); } int getroot(int x) { while (fa[x])x=fa[x]; return x; } bool judge(int x,int y) { makeroot(x); access(y); splay(x,0); return getroot(y)==x; } void link(int x,int y) { if (!judge(x,y)) { makeroot(x); pf[x]=y; } } void cut(int x,int y) { if (judge(x,y)) { makeroot(x); access(y); splay(x,0); t[x][1]=fa[y]=pf[y]=0; update(x); } } int main() { //freopen("readin.txt","r",stdin); n=read(),m=read(); a[0].val=a[0].sum=0; for (int i=1;i<=n;i++) { splay(i,0); a[i].val=read(); update(i); } while (m--) { int z=read(),x=read(),y=read(); if (z==0) { makeroot(y); access(x); splay(x,0); printf("%d\n",a[x].sum); } else if (z==1)link(x,y); else if (z==2)cut(x,y); else { splay(x,0); a[x].val=y; update(x); } } }
上面的都是裸的LCT題目,直接維護
因此接下來的例題不會再有裸題
Description
爲了獲得書法你們的真傳,小 E 同窗下定決心去拜訪住在魔法森林中的隱士。魔法森林能夠被當作一個包含 n 個節點 m
條邊的無向圖,節點標號爲1,2,3, … , n,邊標號爲 1,2,3, … , m。初始時小 E 同窗在 1 號節點,隱士則住在 n
號節點。小 E 須要經過這一片魔法森林,纔可以拜訪到隱士。魔法森林中居住了一些妖怪。每當有人通過一條邊的時候,這條邊上的妖怪就會對其發起攻擊。 幸運的是, 在 1 號節點住着兩種守護精靈: A
型守護精靈與B 型守護精靈。小 E 能夠藉助它們的力量,達到本身的目的。只要小 E 帶上足夠多的守護精靈, 妖怪們就不會發起攻擊了。具體來講, 無向圖中的每一條邊 ei 包含兩個權值 ai 與 bi 。
若身上攜帶的 A 型守護精靈個數很多於 ai ,且 B 型守護精靈個數很多於 bi
,這條邊上的妖怪就不會對經過這條邊的人發起攻擊。當且僅當經過這片魔法森林的過程當中沒有任意一條邊的妖怪向小 E 發起攻擊,他才能成功找到隱士。因爲攜帶守護精靈是一件很是麻煩的事,小 E 想要知道, 要可以成功拜訪到隱士,最少須要攜帶守護精靈的總個數。守護精靈的總個數爲 A
型守護精靈的個數與 B 型守護精靈的個數之和。Input
輸入文件的第 1 行包含兩個整數 n, m,表示無向圖共有 n 個節點, m 條邊。
接下來 m 行,第 i + 1 行包含 4 個正整數 Xi, Yi, ai, bi, 描述第 i
條無向邊。其中Xi與Yi爲該邊兩個端點的標號,ai與bi的含義如題所述。注意數據中可能包含重邊與自環。
Output
輸出一行一個整數:若是小 E 能夠成功拜訪到隱士,輸出小 E 最少須要攜帶的守護精靈的總個數;若是不管如何小 E
都沒法拜訪到隱士,輸出「-1」 (不含引號) 。Sample Input
【樣例輸入 1】
4 5
1 2 19 1
2 3 8 12
2 4 12 15
1 3 17 8
3 4 1 17
【樣例輸入 2】
3 1
1 2 1 1
Sample Output
【樣例輸出 1】
32
【樣例輸出 2】
-1
Data Constraint
Hint
【樣例說明 1】
若是小 E 走路徑 1→2→4,須要攜帶 19+15=34 個守護精靈;
若是小 E 走路徑 1→3→4,須要攜帶 17+17=34 個守護精靈;
若是小 E 走路徑 1→2→3→4,須要攜帶 19+17=36 個守護精靈;
若是小 E 走路徑 1→3→2→4,須要攜帶 17+15=32 個守護精靈。
綜上所述,小 E 最少須要攜帶 32 個守護精靈。
【樣例說明 2】
小 E 沒法從 1 號節點到達 3 號節點,故輸出-1。
好像MST的樣子,但思考一下就能夠發現kruskal是錯的
正解LCT,但據說SPFA也可AC?
維護動態增刪邊MST就能夠了
注意此處維護邊權,就按日常套路,把邊當作點連起來,邊權當作點權
首先將 升序排序,保證 最優,接下來維護 的MST,怎麼弄呢?
咱們從新定義一下 ,爲 節點子樹內權值最大的點的標號
若是當前第 條邊的 小於當前LCT裏面 最大的邊,咱們不就能夠把這條邊換掉了?
經過查找 來查找並切斷那一條較劣的邊便可
而後中途只要 和 是聯通的,就統計答案
搞定
#include<bits/stdc++.h> #define MAXN 200001 #define MAXM 500001 #define INF 1000000007 using namespace std; int t[MAXN][2]; int fat[MAXN],fa[MAXN],pf[MAXN],st[MAXN]; char s[10]; int n,m,ans; struct node { int val,mx,size; bool rev; }a[MAXN]; struct edge { int x,y,a,b; }f[MAXM]; bool cmp(edge x,edge y) { return x.a<y.a; } int getfa(int x) { return !fat[x]?x:fat[x]=getfa(fat[x]); } int read() { int x=0,f=1; char ch=getchar(); while (ch<'0' || '9'<ch) { if (ch=='-')f=-1; ch=getchar(); } while ('0'<=ch && ch<='9') { x=x*10+ch-'0'; ch=getchar(); } return x*f; } void reverse(int x) { if(x) { a[x].rev^=1; swap(t[x][0],t[x][1]); } } void down(int x) { if