NOI2013 快餐厅

Mr_cold

2018-08-16 16:37:49

Solution

作为一道NOI题,这道题是一道好(难)题,这道题给了一张N点N边的图,让你在图中找一点到图中最远点最近,输出这个距最远点最近的距离,首先问题降低,如果这是一棵树,求一点到最远点的最短距离,那其实就是树的直径的一半,但是这道题是一颗环套树,所以现在分两种情况 ### 1 这个环套树的最长路径不经过环 这种情况是比较简单的,我们首先通过一些手段求出这个环,并记录环的信息,(方法:tarjan,dfs,并查集);然后枚举每一个环上的点,对这些点下的子树求直径,取这些直径的最大值,记为ans1. 以上操作找环是O(n+m),对所有环上的点求子树是O(n)的,所以这一步总复杂度O(n+m); 时间复杂度的原因:每个点只便利了一次 ### 2 这个环套树的最长路径经过环 这种情况是比较复杂的,O(n * n) 60分算法是枚举环上的断点,是无法AC的,因此需要优化。 规定环上每个点为1--> circle_cnt A[i]表示(前缀链长度+当前换上节点子树最大深度) B[i]表示(前缀中两个点子树的最大深度+两点之间的距离) C[i]表示(后缀链长度+当前换上节点子树最大深度) D[i]表示(后缀中两个点子树的最大深度+两点之间的距离) 因此当我们枚举到断裂环上i与i+1点之间的边时, 答案为max(max(B[i],D[i]),A[i]+C[i+1]+(1-->circle_cnt点的边权)); 这个式子的含义是 取 max((前缀中的最大直径),(后缀中的最大直径),(前缀i与后缀i+1跨过1和circle_cnt点之间的边所组成的直径)) 显然的我们要保留这里求出的最小值,因为这里求出的路径是所有路径都有可能,不一定是两点间的最短路,这里我就有疑问,但就像第一个样例,最长的路是3->1->4->2距离为5,事实上有3->1->2->4距离为4的路存在,这样无疑缩短了求的直径。 记这个最小值为ans2.这个步骤复杂度时O(circle_cnt) 最终输出max(ans1,ans2)/2; How interesting it is! 以上就是主要思路,对于经过环上的路径也可以通过线段树来求,但是没有这种方法更优。 代码如下 ~~~ #include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<string> #include<queue> #include<cmath> #include<map> using namespace std; typedef long long ll; const int maxn=100000+10; const int inf=0x3f3f3f3f; const int mod=1e9+7; int n; struct Edge{ int to,from,cost; }edge[maxn<<1]; int last[maxn<<1],cnt=0; int huan[maxn],huan_cnt=0,huan_zhi[maxn],fa[maxn],cost[maxn],huan_sign[maxn]; int dfn[maxn],timee=0; ll dis[maxn]; ll ans=0,ans1=0; ll A[maxn],B[maxn]; //A[i]表示 前缀中最大的一条链+当前节点树的最大深度 //B[i]表示前缀中两棵树的最大深度+这两个节点间的距离 ll C[maxn],D[maxn]; //这是后缀做的 //因此当断换上第i与i+1条边时 ans=max(max(B[i],D[i+1]),A[i]+C[i+1]+1-->cnt的边权和) inline int read(){ int a=1,b=0;char c=getchar(); while(c>'9'||c<'0'){if(c=='-') a=-1;c=getchar();} while(c>='0'&&c<='9'){b=(b<<3)+(b<<1)+c-'0';c=getchar();} return a*b; } inline void print(ll x){ if(x<0) x=-x,putchar('-'); if(x>9) print(x/10); putchar(x%10+'0'); } inline void pre(){ } inline void add(int x,int y,int z){ edge[++cnt].to=y; edge[cnt].cost=z; edge[cnt].from=last[x]; last[x]=cnt; } inline void input(){int xx,yy,zz; n=read(); for(int i=1;i<=n;++i) { fa[i]=i; xx=read(),yy=read(),zz=read(); add(xx,yy,zz); add(yy,xx,zz); } } inline void dfs(int now){//此处找环并记录 dfn[now]=++timee; for(int i=last[now];i;i=edge[i].from) { int to=edge[i].to; if(to!=fa[now]) { if(!dfn[to]) { fa[to]=now; cost[to]=edge[i].cost; dfs(to); } else if(dfn[to]>dfn[now]) { for(;to!=now;to=fa[to]) { huan_sign[to]=1; huan[++huan_cnt]=to; huan_zhi[huan_cnt]=cost[to]; } huan_sign[now]=1; huan[++huan_cnt]=now; huan_zhi[huan_cnt]=edge[i].cost; } } } } inline void DP(int now,int father){//dis[i]表示i所在的子树上的直径 for(int i=last[now];i;i=edge[i].from) { int to=edge[i].to; if(!huan_sign[to]&&to!=father) { DP(to,now); ans=max((ll)dis[now]+dis[to]+edge[i].cost,ans); dis[now]=max(dis[now],dis[to]+edge[i].cost); } } } inline void solve(){ dfs(1); for(int i=1;i<=huan_cnt;++i) DP(huan[i],0); ll sum=0,maxx=0; for(int i=1;i<=huan_cnt;++i) { sum+=huan_zhi[i-1]; A[i]=max(A[i-1],dis[huan[i]]+sum); B[i]=max(B[i-1],sum+maxx+dis[huan[i]]); maxx=max(maxx,dis[huan[i]]-sum); } sum=maxx=0; int tmp=huan_zhi[huan_cnt];huan_zhi[huan_cnt]=0; for(int i=huan_cnt;i>=1;--i) { sum+=huan_zhi[i]; C[i]=max(C[i+1],dis[huan[i]]+sum); D[i]=max(D[i+1],sum+maxx+dis[huan[i]]); maxx=max(maxx,dis[huan[i]]-sum); } ll tep; ans1=B[huan_cnt]; for(int i=1;i<huan_cnt;++i) { tep=max(B[i],D[i+1]); tep=max(tep,A[i]+C[i+1]+tmp); ans1=min(tep,ans1); } ans=max(ans,ans1); if(ans&1) print(ans>>1),puts(".5"); else print(ans>>1),puts(".0"); } inline void output(){ } int main(){int T; T=1; while(T--) { pre(); input(); solve(); output(); } return 0; } ~~~