题面
因为 OB 今年拿下 4 块金牌,学校赞助扩建劳模办公室为劳模办公室群,为了体现 OI 的特色,办公室群被设计成了树形(n 个点 n − 1 条边的无向连通图),由于新建的办公室太大以至于要将奖杯要分放在两个不同的地方以便同学们丢硬币进去开光,OB 想请你帮帮他看看奖杯放在哪两个办公室使得在任意一个在劳模办公室做题的小朋友能最快地找到奖杯来开光。
一句话题意:给出一个 n 个点的树,在两个合适且不同的点放上奖杯,使得每个点到最近的奖杯距离最大值最小
对于前 60% 的数据,n ≤ 100。
对于前 80% 的数据,n ≤ 2000。
对于 80% 的数据,保证树的形态随机。
对于 100% 的数据,保证 3 ≤ n ≤ 200000。
分析
最大值最小,可以看出是二分。
正向验证行不通,考虑反向验证。
显然放叶子结点是最不优秀的,贪心地想,放的结点刚好能管到叶子结点肯定是优秀的
于是定义dp[u]表示u到奖杯的最近距离,叶子节点的距离为二分的值k,而如果当dp[u]==0就一定要放了
如果最后放的奖杯数量比2个多,肯定是不行的
代码
#include<bits/stdc++.h> using namespace std; #define INF 123456789 #define N 200020 int dp[N],vis[N],first[N],d[N]; int n,m,x,y,op,cnt,ans,k,tot,l,r; struct email { int u,v; int nxt; }e[N*4]; inline void add(int u,int v) { e[++cnt].nxt=first[u];first[u]=cnt; e[cnt].u=u;e[cnt].v=v; } template<class T> inline void read(T &x) { x=0; static char c=getchar(); while(c<'0'||c>'9') c=getchar(); while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar(); } inline void dfs(int u,int f) { int tmp=0,fg=-INF; for(int i=first[u];i;i=e[i].nxt) { int v=e[i].v; if(v==f)continue; ++tmp;dfs(v,u); if(dp[v]>0)dp[u]=min(dp[u],dp[v]-1); else fg=max(fg,dp[v]); } if(tmp==0)dp[u]=k;//在叶结点上赋到奖杯距离为k else if(dp[u]==0)++tot;//必须放置奖杯 if(fg!=-INF) { if(dp[u]==INF)dp[u]=k; if(fg+dp[u]>0)dp[u]=fg-1; } } int check() { for(int i=1;i<=n;i++)dp[i]=INF; tot=0; dfs(1,0); if(dp[1]>0)tot++; if(tot<=2)return true; return false; } int main() { read(n); ans=INF; for(int i=1;i<n;i++) { int u,v; read(u),read(v); add(u,v);add(v,u); } l=1,r=n; while(l<=r) { k=l+r>>1; if(check())r=k-1,ans=k; else l=k+1; } printf("%d ",ans); return 0; }