[SDOI2013] 直径
题目描述
小Q最近学习了一些图论知识。根据课本,有如下定义。树:无回路且连通的无向图,每条边都有正整数的权值来表示其长度。如果一棵树有N个节点,可以证明其有且仅有N-1 条边。
路径:一棵树上,任意两个节点之间最多有一条简单路径。我们用 dis(a,b)表示点a和点b的路径上各边长度之和。称dis(a,b)为a、b两个节点间的距离。
直径:一棵树上,最长的路径为树的直径。树的直径可能不是唯一的。
现在小Q想知道,对于给定的一棵树,其直径的长度是多少,以及有多少条边满足所有的直径都经过该边。
输入输出格式
输入格式:
第一行包含一个整数N,表示节点数。 接下来N-1行,每行三个整数a, b, c ,表示点 a和点b之间有一条长度为c的无向边。
输出格式:
共两行。第一行一个整数,表示直径的长度。第二行一个整数,表示被所有直径经过的边的数量。
输入输出样例
说明
【样例说明】 直径共有两条,3 到2的路径和3到6的路径。这两条直径都经过边(3, 1)和边(1, 4)。
对于100%的测试数据:2<=N<=200000,所有点的编号都在1..N的范围内,边的权值<=10^9。
这种题真的没意思,,,就是暴力分情况然后讨论讨论讨论讨论讨论。。。。。。。。。。
一个大概的思路就是,看一看删去每条边之后剩下的两个联通块的直径是不是比原树的直径都小,如果是的话这条边就是不能割的。
但是怎么快速计算这个玩意呢?看起来好像要把每条边都割一遍然后dfs一次,复杂度大的上天。
不过在dfs的众多套路里还是有能 O(N) 算出这类东西的方法的,简而言之就是 dfs两次:第一次预处理出一些需要的信息,然后第二次就带着从爸爸那里继承来的信息向下更新。
假如我们要割去 (u,v) ,其中u是v的爸爸,那么我们只需要知道两个新的联通块的直径是多少就行了。
以v为根的子树的直径好算,第一遍dfs就可以处理出来;总的树除去以v为根的子树的直径怎么算呢?
考虑这一部分只可能是 从父亲那里继承来的直径, 或者 是经过点u的最长路径(且不能经过v) ,或者是来自u其他儿子子树内部的直径。。。。
所以一个前缀一个后缀统计统计就好啦(可以把父亲当成u的第0个儿子,方便处理一些)。
先贴一个用来对拍的暴力(这个暴力在第二问答案比较小的情况下是可以艹爆标程的2333,原理就是bfs两遍找直径的算法,可以不断迭代,所以你可以叫它高级暴力233):
#include<bits/stdc++.h> #define ll long long using namespace std; const int maxn=200005; #define pb push_back int to[maxn*2],ne[maxn*2],val[maxn*2],num=1; int n,hd[maxn],p[maxn],T,c[maxn*2],L; bool v[maxn],b[maxn]; ll d[maxn],ans; queue<int> q; inline void add(int x,int y,int z){ to[++num]=y,ne[num]=hd[x],hd[x]=num,val[num]=z;} inline void update(int x){ c[x-(x&1)]++;} inline void calc(){ for(int i=2;i<=num;i+=2) if(c[i]==T) L++; } inline void solve(int x){ vector<int> now; b[x]=1,q.push(x); memset(v,0,sizeof(v)); d[x]=0,v[x]=1; int P=0,S=x; while(!q.empty()){ x=q.front(),q.pop(); if(!b[x]){ if(!P) P=x,now.pb(x); else if(d[x]>d[P]) now.clear(),now.pb(x),P=x; else if(d[x]==d[P]) now.pb(x); } for(int i=hd[x];i;i=ne[i]) if(!v[to[i]]){ v[to[i]]=1,d[to[i]]=d[x]+(ll)val[i]; p[to[i]]=i,q.push(to[i]); } } if(!ans) ans=d[P]; else if(d[P]<ans) return; for(int i=now.size()-1;i>=0;i--){ x=now[i]; if(b[x]) continue; T++; while(x!=S){ update(p[x]); x=to[p[x]^1]; } } for(int i=now.size()-1;i>=0;i--) if(!b[now[i]]) solve(now[i]); } int main(){ freopen("data.in","r",stdin); freopen("ans.out","w",stdout); int uu,vv,ww; scanf("%d",&n); for(int i=1;i<n;i++){ scanf("%d%d%d",&uu,&vv,&ww); add(uu,vv,ww),add(vv,uu,ww); } q.push(1),v[1]=1; int x,P=0; while(!q.empty()){ x=q.front(),q.pop(); if(!P||d[x]>d[P]) P=x; for(int i=hd[x];i;i=ne[i]) if(!v[to[i]]){ v[to[i]]=1,d[to[i]]=d[x]+(ll)val[i]; q.push(to[i]); } } solve(P); calc(); printf("%lld\n%d\n",ans,L); return 0; }
然后这是数据生成器(因为边权都不一样的时候不太好构造有很多直径的树,树深大的情况亦是如此,所以干脆直接随机):
#include<bits/stdc++.h> #define ll long long using namespace std; int main(){ freopen("data.in","w",stdout); srand(time(0)); int n=100000; cout<<n<<endl; for(int i=2;i<=n;i++) printf("%d %d %d\n",i,rand()%(i-1)+1,1); return 0; }
正解比暴力短,哎。。。。
#include<bits/stdc++.h> #define ll long long using namespace std; const int maxn=200005; #define pb push_back int to[maxn*2],ne[maxn*2],val[maxn*2]; int n,hd[maxn],num=1,L,D[maxn]; ll ans,mxl[maxn],mx[maxn]; inline void add(int x,int y,int z){ to[++num]=y,ne[num]=hd[x],hd[x]=num,val[num]=z,D[y]++;} void Fdfs(int x,int fa){ for(int i=hd[x];i;i=ne[i]) if(to[i]!=fa){ Fdfs(to[i],x); mx[x]=max(mx[x],mx[to[i]]); mx[x]=max(mx[x],mxl[x]+mxl[to[i]]+(ll)val[i]); mxl[x]=max(mxl[x],mxl[to[i]]+(ll)val[i]); } } void Sdfs(int x,int fa,ll ml,ll Q){ if(D[x]==1&&fa) return; ll qzm[D[x]+2],hzm[D[x]+2]; ll qzl[D[x]+2],hzl[D[x]+2]; int cnt=0; for(int i=hd[x];i;i=ne[i]) if(to[i]!=fa){ cnt++,qzl[cnt]=hzl[cnt]=mxl[to[i]]+(ll)val[i]; qzm[cnt]=hzm[cnt]=mx[to[i]]; } qzl[0]=ml,qzm[0]=Q; hzl[cnt+1]=hzm[cnt+1]=0; for(int i=1;i<=cnt;i++) qzm[i]=max(qzl[i]+qzl[i-1],max(qzm[i],qzm[i-1])),qzl[i]=max(qzl[i],qzl[i-1]); for(int i=cnt-1;i;i--) hzm[i]=max(hzl[i]+hzl[i+1],max(hzm[i],hzm[i+1])),hzl[i]=max(hzl[i],hzl[i+1]); cnt=0; for(int i=hd[x];i;i=ne[i]) if(to[i]!=fa){ cnt++; ll now=max(qzl[cnt-1]+hzl[cnt+1],max(qzm[cnt-1],hzm[cnt+1])); if(max(now,mx[to[i]])<ans) L++; Sdfs(to[i],x,max(qzl[cnt-1],hzl[cnt+1])+(ll)val[i],now); } } inline void solve(){ Fdfs(1,0),ans=mx[1]; Sdfs(1,0,0,0); } int main(){ freopen("data.in","r",stdin); freopen("data.out","w",stdout); int uu,vv,ww; scanf("%d",&n); for(int i=1;i<n;i++) scanf("%d%d%d",&uu,&vv,&ww),add(uu,vv,ww),add(vv,uu,ww); solve(); printf("%lld\n%d\n",ans,L); return 0; }