树上最长链 Farthest Nodes in a Tree LightOJ - 1094 && [ZJOI2007]捉迷藏 && 最长链
树上最远点对(树的直径)
做法1:树形dp
最长路一定是经过树上的某一个节点的。
因此:
an1[i],an2[i]分别表示一个点向下的最长链和次长链,次长链不存在就设为0;这两者很容易求
an3[i]表示i为根的子树中的答案;an3[u]=max(max{an3[v]}(v是u的子节点),an1[u]+an2[u])
1 #include<cstdio> 2 #include<cstring> 3 #include<queue> 4 #include<algorithm> 5 using namespace std; 6 typedef long long LL; 7 struct Edge 8 { 9 LL to,dis,next; 10 }e[60010]; 11 LL ne,f1[30010]; 12 bool vis[30010]; 13 LL ans[30010],anss,T,TT,n; 14 void dfs(LL u) 15 { 16 vis[u]=true; 17 LL k=f1[u],v,ma1=0,ma2=0; 18 priority_queue<LL> q; 19 while(k!=0) 20 { 21 v=e[k].to; 22 if(!vis[e[k].to]) 23 { 24 dfs(v); 25 q.push(ans[v]+e[k].dis); 26 } 27 k=e[k].next; 28 } 29 if(!q.empty()) 30 ma1=q.top(),q.pop(); 31 if(!q.empty()) 32 ma2=q.top(),q.pop(); 33 anss=max(anss,ma1+ma2); 34 ans[u]=ma1; 35 } 36 int main() 37 { 38 LL i,u,v,w; 39 scanf("%lld",&TT); 40 for(T=1;T<=TT;T++) 41 { 42 memset(vis,0,sizeof(vis)); 43 memset(ans,0,sizeof(ans)); 44 ne=0; 45 anss=0; 46 memset(f1,0,sizeof(f1)); 47 scanf("%lld",&n); 48 for(i=1;i<n;i++) 49 { 50 scanf("%lld%lld%lld",&u,&v,&w); 51 e[++ne].to=v; 52 e[ne].next=f1[u]; 53 e[ne].dis=w; 54 f1[u]=ne; 55 e[++ne].to=u; 56 e[ne].next=f1[v]; 57 e[ne].dis=w; 58 f1[v]=ne; 59 } 60 dfs(0); 61 printf("Case %lld: %lld\n",T,anss); 62 } 63 return 0; 64 }
做法2:贪心做法&证明
只需要从任意点a1出发遍历整张图找到离a1最远的点u,再从u出发找到离u最远的点v,u到v的路径就是最长路。
同时可以发现一点:到树上任意一点的最长链,一定是这一点到u、v的链之一。
以上得到了一个性质
设路径(s,t)为当前树上任意一条直径,从树上任意一点u出发,到达任意一点v,设len(u,v)为u到v最短路径的长度,则max(len(u,s),len(u,t))>=len(u,v)对于树上任意一点u,链(u,s)和(u,t)之中,至少有一条是所有以u为端点的链中最长的
根据这个性质,可以证明另一个结论:
现在有两棵树,它们分别有直径(s1,t1)和(s2,t2),设两棵树上分别有一个点,为a,b,在a,b之间连一条边得到一个新的无根树,那么新树一定有至少一条直径,其两个端点都在s1,t1,s2,t2中
证明&其他说明:https://blog.csdn.net/rzo_kqp_orz/article/details/52280811
(证明好像不难,反证+分类讨论)
并且可以发现,这些结论还可以向带边/点权(边/点权非负)的情况推广
比如[ZJOI2007]捉迷藏,有一种非常特殊的做法
是基于以上结论的一个推论(就是把合并树换成合并同一棵已知树上的点集)
有一棵给定的树和树上的两个点集s1,s2,设s1,s2的一条直径分别为(a1,b1),(a2,b2),那么点集(s1∪s2)至少有一条直径,其两个端点都在a1,b1,a2,b2中(点集直径定义自行脑补23333)
证明不太会...大概是先把点集都“补全“成连通块,显然直径不变,然后合并,如果合并后不连通那么显然满足,如果连通且两个点集不相交则按照前面的证法,如果连通且点集相交则除去两个块公共部分然后按两个块各自直径端点位置分类讨论,然后再把”补全“时加进去的点去掉,显然直径仍然不变
具体见题解https://www.luogu.org/blog/user7035/solution-p2056
使用欧拉序lca,可以O(1)求两点距离,可以m*log(n)完成此题
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 #include<vector> 5 using namespace std; 6 #define fi first 7 #define se second 8 #define mp make_pair 9 #define pb push_back 10 typedef long long ll; 11 typedef unsigned long long ull; 12 typedef pair<int,int> pii; 13 typedef pair<int,pii> pip; 14 struct E 15 { 16 int to,nxt; 17 }e[200100]; 18 int f1[100100],ne; 19 int /*p1[100100],*/p2[200100],pl2[100100],fa[101000],dep[101000]; 20 int l2x[200100],lft[20]; 21 int n,q; 22 void dfs(int u) 23 { 24 /*p1[++p1[0]]=u;*/p2[++p2[0]]=u;pl2[u]=p2[0]; 25 for(int k=f1[u];k;k=e[k].nxt) 26 if(e[k].to!=fa[u]) 27 { 28 fa[e[k].to]=u; 29 dep[e[k].to]=dep[u]+1; 30 dfs(e[k].to); 31 p2[++p2[0]]=u; 32 } 33 } 34 namespace ST 35 { 36 int st[200100][19],l2n=18; 37 void pre() 38 { 39 int i,j; 40 for(i=1;i<=p2[0];i++) st[i][0]=p2[i]; 41 for(j=1;j<=l2x[p2[0]];j++) 42 { 43 for(i=1;i<=p2[0]-lft[j]+1;i++) 44 { 45 st[i][j]=(dep[st[i][j-1]]>dep[st[i+lft[j-1]][j-1]]) 46 ?st[i+lft[j-1]][j-1]:st[i][j-1]; 47 } 48 } 49 } 50 int lca(int a,int b) 51 { 52 a=pl2[a];b=pl2[b]; 53 if(a>b) swap(a,b); 54 int k=l2x[b-a+1]; 55 return dep[st[a][k]]>dep[st[b-lft[k]+1][k]] 56 ?st[b-lft[k]+1][k]:st[a][k]; 57 } 58 int dis(int a,int b) 59 { 60 return dep[a]+dep[b]-2*dep[lca(a,b)]; 61 } 62 } 63 using ST::dis; 64 namespace S 65 { 66 const int N=300000; 67 bool ok[N]; 68 pii n1[N]; 69 #define lc (num<<1) 70 #define rc (num<<1|1) 71 void upd(int num) 72 { 73 ok[num]=ok[lc]|ok[rc]; 74 if(!ok[lc]) {n1[num]=n1[rc];return;} 75 if(!ok[rc]) {n1[num]=n1[lc];return;} 76 int t[]={n1[lc].fi,n1[lc].se,n1[rc].fi,n1[rc].se}; 77 pip ans(-1,mp(-1,-1)); 78 int i,j; 79 for(i=0;i<4;i++) 80 { 81 for(j=i+1;j<4;j++) 82 { 83 ans=max(ans,mp(dis(t[i],t[j]),mp(t[i],t[j]))); 84 } 85 } 86 n1[num]=ans.se; 87 } 88 void build(int l,int r,int num) 89 { 90 if(l==r) {ok[num]=1;n1[num].fi=n1[num].se=l;return;} 91 int mid=l+((r-l)>>1); 92 build(l,mid,lc);build(mid+1,r,rc); 93 upd(num); 94 //printf("%d %d %d %d\n",l,r,n1[num].fi,n1[num].se); 95 } 96 void change(int L,int l,int r,int num) 97 { 98 if(l==r) {ok[num]^=1;return;} 99 int mid=l+((r-l)>>1); 100 if(L<=mid) change(L,l,mid,lc); 101 else change(L,mid+1,r,rc); 102 upd(num); 103 } 104 } 105 char tmp[10]; 106 int main() 107 { 108 int i,j,a,b; 109 lft[0]=1; 110 for(i=1;i<20;i++) lft[i]=lft[i-1]<<1; 111 for(i=1,j=0;i<=200000;i++) 112 { 113 while(lft[j+1]<=i) j++; 114 l2x[i]=j; 115 } 116 scanf("%d",&n); 117 for(i=1;i<n;i++) 118 { 119 scanf("%d%d",&a,&b); 120 e[++ne].to=b;e[ne].nxt=f1[a];f1[a]=ne; 121 e[++ne].to=a;e[ne].nxt=f1[b];f1[b]=ne; 122 } 123 124 dfs(1);ST::pre(); 125 S::build(1,n,1); 126 scanf("%d",&q); 127 while(q--) 128 { 129 scanf("%s",tmp); 130 if(tmp[0]=='G') 131 { 132 if(S::ok[1]) 133 { 134 printf("%d\n",dis(S::n1[1].fi,S::n1[1].se)); 135 } 136 else 137 { 138 puts("-1"); 139 } 140 141 } 142 else 143 { 144 scanf("%d",&a); 145 S::change(a,1,n,1); 146 } 147 } 148 return 0; 149 }
(然而貌似常数太大,比好多点分还慢)
http://210.33.19.103/contest/881/problem/1
最长链
树是任意两点间仅有一条路径的联通图,树上的一条链定义为两个点之间的路径。在本题中定义一条链的长度为链上所有点的权值和。现有一棵带点权树,要对它进行一些操作,请你在第一次操作前和每一次操作后输出这棵树的最长链。
输入格式:
输入文件longest.in第一行有两个整数n、m,表示树中点的个数,以及操作个数。
接下去n-1行,每行有两个整数a、b,表示有一条连接a、b两个点的边。
接下去n行,每行有一个整数x,按次序表示n个点的初始点权。
接下去m行,每行表示一个操作。若第一个字母为M,则接下去会有两个整数a、x,表示将第a个点的点权修改为b。 否则第一个字母为C,接下去会有4个整数a、b、c、d,表示将连接a、b的一条边删掉,再添上一条连接c、d的边。
输出格式:
输出文件longest.out共有m+1行,表示第一次操作前以及每一次操作后的树的最长链。
样例输入:
3 2
1 2
1 3
-1000 1 1
C 1 2 2 3
M 1 1
样例输出:
1
2
3
数据范围:
20%的数据1 ≤ n,m ≤ 1000。
另外20%的数据,保证任意时刻此树是一条链。
另外20%的数据,保证没有第二种操作。
另外20%的数据,保证点权非负。
100%的数据,1 ≤ n,m ≤ 100000,|x| ≤ 10000。保证数据合法。
等待...