1. 程式人生 > 實用技巧 >cf 1434D. Roads and Ramen (樹上最長偶權鏈)

cf 1434D. Roads and Ramen (樹上最長偶權鏈)

題目連結:傳送門

題目思路:

顯然,最長的偶權鏈的兩個端點中,至少有一個是直徑的端點。

簡單證明一下,

若直徑的權為偶,那麼最長鏈肯定就是直徑

若直徑的權為奇,那麼對於直徑上任意一個點可以把直徑分割為一段奇權鏈和一段偶權鏈

那麼可以使用反證法,假設最長的偶權鏈的兩個端點都不是直徑的端點。如下圖所示:

首先明確一點:一個點在樹上所能搜尋出的最遠的點一定是直徑的端點(也可採用反證法證明)

圖中:ab是直徑,cd是假定的答案(假定的最長偶權樹鏈),lr是重疊部分;bl > cl , ra > rd (不滿足的話c 或 d就是直徑的端點了)

如果重疊部分lr為偶權,bl或ra為一偶一奇(按上圖情況),那麼cl和rd均為偶權,顯然可以用bl去替換掉cl ,cd -> bd(按上圖情況)

如果重疊部分lr為奇權,bl和ra均為偶權,那麼cl和rd一個奇權 一個偶權,那麼勢必可以用bl 或者ar 對cl和rd中的偶權作替換;

證明完畢。

已知最長偶鏈端點一定是直徑的某一端點,可以求出樹的直徑的兩個端點,分別以這兩個端點建樹,用線段樹維護其dfs序,每個點u維護的兩個值,一個是根到u的奇權鏈長度,一個是根到u的偶權鏈長度,由於根到u的路徑是唯一的,奇偶也是唯一的,所以偶權鏈或者奇權鏈的長度有一個為0,另一個則是u到根的距離。

  1 #include<bits/stdc++.h>
  2 /*
  3 #include<cstdio>
  4 #include<cmath>
5 #include<cstring> 6 #include<vector> 7 #include<cctype> 8 #include<queue> 9 #include<algorithm> 10 #include<map> 11 #include<set> 12 */ 13 //#pragma GCC optimize(2) 14 using namespace std; 15 typedef long long LL; 16 typedef unsigned long long
uLL; 17 typedef pair<int,int> pii; 18 typedef pair<LL,LL> pLL; 19 typedef pair<double,double> pdd; 20 const int N=5e5+5; 21 const int M=8e5+5; 22 const int inf=0x3f3f3f3f; 23 const LL mod=1e8+7; 24 const double eps=1e-8; 25 const long double pi=acos(-1.0L); 26 #define ls (i<<1) 27 #define rs (i<<1|1) 28 #define fi first 29 #define se second 30 #define pb push_back 31 #define eb emplace_back 32 #define mk make_pair 33 #define mem(a,b) memset(a,b,sizeof(a)) 34 LL read() 35 { 36 LL x=0,t=1; 37 char ch; 38 while(!isdigit(ch=getchar())) if(ch=='-') t=-1; 39 while(isdigit(ch)){ x=10*x+ch-'0'; ch=getchar(); } 40 return x*t; 41 42 } 43 vector<pii> e[N]; 44 struct edge{int u,v,w;}a[N]; 45 int rt1,rt2,ma; 46 pii ma1[N],ma2[N]; 47 //建兩顆樹(一棵樹需要c、lz、int、out、rk、deep陣列;如果直接用陣列名傳參,引數表就太長了,不如直接做一個結構體) 48 struct node 49 { 50 int c[N<<2][2],lz[N<<2],in[N],out[N],rk[N],dep[N],col[N],tot; 51 inline void pushdown(int i) 52 { 53 swap(c[ls][0],c[ls][1]); 54 swap(c[rs][0],c[rs][1]); 55 lz[ls]^=1; lz[rs]^=1; 56 lz[i]=0; 57 } 58 inline void pushup(int i) 59 { 60 c[i][0]=max(c[ls][0],c[rs][0]); 61 c[i][1]=max(c[ls][1],c[rs][1]); 62 } 63 void build(int i,int l,int r) 64 { 65 c[i][0]=c[i][1]=lz[i]=0; 66 if(l==r) return (void)(c[i][col[rk[l]]]=dep[rk[l]]); 67 int mid=l+r>>1; 68 build(ls,l,mid); 69 build(rs,mid+1,r); 70 pushup(i); 71 } 72 void update(int i,int l,int r,int ll,int rr) 73 { 74 if(ll<=l&&r<=rr) return (void)(swap(c[i][0],c[i][1]),lz[i]^=1); 75 int mid=l+r>>1; 76 if(lz[i]) pushdown(i); 77 if(mid>=ll) update(ls,l,mid,ll,rr); 78 if(mid<rr) update(rs,mid+1,r,ll,rr); 79 pushup(i); 80 } 81 82 void dfs(int u,int pre) 83 { 84 in[u]=++tot; rk[tot]=u; 85 dep[u]=dep[pre]+1; 86 for(auto x:e[u]) 87 { 88 int v=x.fi,w=x.se; 89 if(v==pre) continue; 90 col[v]=col[u]^w; 91 dfs(v,u); 92 } 93 out[u]=tot; 94 } 95 }tr[2]; 96 void dfs2(int u,int pre) 97 { 98 ma1[u]=ma2[u]=mk(0,u); 99 for(auto x:e[u]) 100 { 101 int v=x.fi; 102 if(v==pre) continue; 103 dfs2(v,u); 104 pii t=ma1[v]; 105 if(t>ma1[u]) swap(t,ma1[u]); 106 if(t>ma2[u]) ma2[u]=t; 107 //f[u]=max(f[v],f[u]); 108 } 109 //printf("u = %d , fi = %d , se = %d\n",u,ma1[u].fi,ma2[u].fi); 110 if(ma1[u].fi+ma2[u].fi>ma) ma=ma1[u].fi+ma2[u].fi,rt1=ma1[u].se,rt2=ma2[u].se; 111 ma1[u].fi++; ma2[u].fi++; 112 } 113 114 int main() 115 { 116 int n=read(); 117 for(int i=1;i<n;i++) 118 { 119 int x=read(),y=read(),z=read(); 120 a[i]=edge{x,y,z}; 121 e[x].eb(y,z); 122 e[y].eb(x,z); 123 } 124 dfs2(1,0); 125 // printf("%d %d\n",rt1,rt2); 126 tr[0].dfs(rt1,0); 127 tr[0].build(1,1,n); 128 tr[1].dfs(rt2,0); 129 tr[1].build(1,1,n); 130 int m=read(); 131 for(int i=1;i<=m;i++) 132 { 133 int x=read(),y=a[x].u; 134 if(tr[0].dep[a[x].u]<tr[0].dep[a[x].v]) y=a[x].v; 135 tr[0].update(1,1,n,tr[0].in[y],tr[0].out[y]); 136 y=a[x].u; 137 if(tr[1].dep[a[x].u]<tr[1].dep[a[x].v]) y=a[x].v; 138 tr[1].update(1,1,n,tr[1].in[y],tr[1].out[y]); 139 printf("%d\n",max(tr[0].c[1][0],tr[1].c[1][0])-1); 140 } 141 return 0; 142 }
View Code

每次翻轉某一條邊的權,實際上就是翻轉整個子樹所有點的權,對於單個點來說,就是奇權鏈變成偶權鏈,但是長度不變,只需要交換根到u 的奇權長度和偶權長度即可(延時標記處理)

程式碼: