[BZOJ 3124] 直径
Link:
Solution:
对于第二问,要先推出几个性质:
1、这些边在一条直径上
2、这些边一定是连续的
这类性质主要就靠瞎蒙再用反证法证一证就好了
(证不出来感性认知一下就直接上吧)
接下来只要在任意一个直径上寻找连续的可行边即可
设直径的两个端点分别为$S,T$,可以使用尺取法的思路,维护左右两个边界
对于每个点判断其是否有一条不经过直径但与该点到$S/T$距离相同的路径,如果有,则将左/右边界移动
(这里有一个剪枝,如果是从$T->S$判断,只要有点$x$有第二条距离为$dist(S,x)$的路径,则可以直接退出,可用反证法证明)
这里的思路有些类似于BZOJ 2282
但此题对于每个节点的判断不具有决策单调性,因此不能二分
Code:
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int MAXN=2e5+10; bool on_dia[MAXN]; struct edge{int nxt,to;ll w;}e[MAXN<<2]; ll dia,dist[MAXN]; int n,head[MAXN],rt1,rt2,f[MAXN],tot=0,cur=0; void add_edge(int from,int to,int w) { e[++tot].nxt=head[from];e[tot].to=to; e[tot].w=w;head[from]=tot; } void dfs(int x,int anc) //求直径 { for(int i=head[x];i;i=e[i].nxt) { if(e[i].to==anc) continue; f[e[i].to]=x;dist[e[i].to]=dist[x]+e[i].w; dfs(e[i].to,x); } } void find_max(int x,int anc) //计算非直径上的最远距离 { if(dist[x]>cur) cur=dist[x]; for(int i=head[x];i;i=e[i].nxt) { if(e[i].to==anc || on_dia[e[i].to]) continue; dist[e[i].to]=dist[x]+e[i].w; find_max(e[i].to,x); } } int main() { scanf("%d",&n); for(int i=1;i<n;i++) { int x,y;ll z;scanf("%d%d%lld",&x,&y,&z); add_edge(x,y,z);add_edge(y,x,z); } dfs(1,0); for(int i=1;i<=n;i++) if(dist[i]>dist[rt1]) rt1=i; dist[rt1]=0;dfs(rt1,0); for(int i=1;i<=n;i++) if(dist[i]>dist[rt2]) rt2=i; on_dia[rt1]=true;dia=dist[rt2]; for(int i=rt2;i!=rt1;i=f[i]) on_dia[i]=true; printf("%lld\n",dia); int l=rt1,r=rt2; for(int i=f[rt2];i!=rt1;i=f[i]) { ll pl=dist[i],pr=dia-dist[i]; dist[i]=cur=0;find_max(i,0); if(cur==pl) {l=i;break;} //如果左端有新路径则直接退出 if(cur==pr) r=i; } int res=0; for(int i=r;i!=l;i=f[i]) res++; printf("%d",res); return 0; }
Review:
1、$dfs$时的代码技巧
如果递归结束后不好确定经过哪些点,最好在递归过程中更新结果
2、推结论
多用反证法和假设法检验某种情况是否可能出现
尽可能利用结论进行剪枝