题外话:
这是制杖 yd 的交流题目
题面 https://www.luogu.org/problemnew/show/P4654
首先把捕鼠夹所在的点提出来当根, 然后这变成了一棵有根树, 我们先来看耗子移动的影响
可以发现耗子往下走就回不来了, 而且最后还会被困在一个叶子上, 那么这个时候我们把那个子树到根的路径砍成一条链 (显然不砍成链耗子可以半路跑进岔路里, 至少要你再清理一次, 肯定不如砍了优) 再把耗子放出来就可以了. 而耗子往上走我们是管不了的(指不能阻止它往上走, 但是可以砍旁边的分叉, 一会具体说), 毕竟我们不能把树砍断了, 那耗子就到不了夹子了
那么耗子的决策就明确了, 它往上走一段之后或者直接找到一个子树钻进去, 这个子树应该是我们把子树的根到树的根砍成链需要操作次数最多的, 而我们和耗子的博弈就是尽量阻止它钻进需要次数多的子树里. 现在考虑求耗子钻进一个点的子树之后我们的最少操作数: 先定义 $intr[i]$ 表示耗子钻进 $i$ 我们砍完边再把它放回 $i$ 所需的最小次数, 这样方便转移, 转移的话因为我们每次只能砍一条边, 就是一个点儿子里 $intr$ 的不严格次大值 (最大值被我们砍了, 如果没有次大值就是零) 加上这个点的度数 (这个点也需要砍光) 再减 $1$(这个点和父亲之间的边).
注意这时的状态还不是到根的答案, 要求到根的答案我们还要处理一个 $road[i]$ 表示从 $i$ 到根路径上的岔路. 这里我们把耗子所在的初始点到根的链拎出来, 然后扫一遍就可以求出来 $road$, 这样每个点到根的时间就是 $intr[i]+road[fa[i]]+(fa[i]==start)$,$+(fa[i]==start)$ 是因为初始这个点再往下走相当于在子树里面又走了一步, 还要把这步也擦掉 . 然后我们发现直接求并不好求, 因为并不容易知道耗子到底要怎么跑, 但是我们可以二分一个答案, 然后检验耗子能不能跑进一个超过当前时间的子树即可
- #include<cstdio>
- #include<cstring>
- #include<algorithm>
- using namespace std;
- const int N=1000005;
- int p[N],noww[2*N],goal[2*N],path[N],deg[N];
- int intr[N],road[N],tort[N],far[N],dep[N];
- //intr(into-tree): 进入这个点的子树又回来的最小次数
- //road: 这个点到根路径上的岔路的数目
- //tort(to-root): 这个点到根的最小操作次数
- int n,m,l,r,t1,t2,trp,rat,cnt,mid,ans,ops;
- void Link(int f,int t)
- {
- noww[++cnt]=p[f],p[f]=cnt;
- goal[cnt]=t,deg[t]++;
- }
- void DFS(int nde,int fth,int dth)
- {
- int max1=0,max2=0,tmp=-1;
- far[nde]=fth,dep[nde]=dth;
- for(int i=p[nde];i;i=noww[i])
- if(goal[i]!=fth)
- {
- DFS(goal[i],nde,dth+1),tmp=intr[goal[i]];
- if(tmp>=max1) max2=max1,max1=intr[goal[i]];
- else if(tmp>max2) max2=tmp;
- }
- if(~tmp) intr[nde]=max2+deg[nde]-1;// 次小值更新 intr, 叶子节点的是 0
- if(nde==rat)
- {
- while(far[nde])
- path[++m]=nde,nde=far[nde];// 把耗子初始位置到根的链拎出来
- for(int i=m,lst=0;i;i--,lst=nde)
- nde=path[i],road[nde]=road[lst]+deg[nde]-2;// 扫一遍把岔路数量求出来
- }
- }
- bool check(int x)
- {
- int lst=0;
- for(int i=1;i<=m;i++)
- {
- int nde=path[i],tmp=0;
- for(int j=p[nde];j;j=noww[j])
- if(goal[j]!=far[nde]&&goal[j]!=lst)
- tmp+=(road[nde]+intr[goal[j]]+(i==1)>x);
- // 这里 +(i==1)是说初始这个点再往下走相当于在子树里面又走了一步, 还要把这步也擦掉
- x-=tmp,ops+=tmp,lst=nde;
- if(x<0||ops>dep[rat]-dep[nde]+1) return false;// 注意透支次数也是不行的
- }
- return true;
- }
- int main()
- {
- scanf("%d%d%d",&n,&trp,&rat);
- for(int i=1;i<n;i++)
- {
- scanf("%d%d",&t1,&t2);
- Link(t1,t2),Link(t2,t1);
- }
- DFS(trp,0,1),l=0,r=1000000;
- while(l<=r)
- {
- mid=(l+r)/2,ops=0;
- if(check(mid)) r=mid-1,ans=mid;
- else l=mid+1;
- }
- printf("%d",ans);
- return 0;
- }
- View Code
来源: http://www.bubuko.com/infodetail-2901001.html