Luogu P4654 [CEOI2017]Mousetrap
比较有趣的一道题,AGC做多了感觉想这种题的能力也变强了吧
首先我们要看对题目:题目要求的是最小化管理员操作的次数,而并非两者博弈进行的轮次
容易想到我们可以把陷阱设为整棵树的根,容易发现对于起点到根的路径最后老鼠是一定要走的,因此对于老鼠来说最优的决策一定是要向下走
然后我们发现,对于管理员来说,完全可以先保持旁观,因为老鼠向下走迟早会把自己卡在一个叶节点,此时管理员只需要把老鼠到根节点的路径上的其它分支都堵住,然后把老鼠到根的路径上弄脏的边都擦掉即可
考虑求出两个东西,(up_x)表示把(x)到根的路径上的所有分支都堵住的操作次数;(f_x)表示把老鼠卡死在(x)的操作次数(老鼠能走的边要么被堵住了要么被弄脏了)
(up_x)的转移比较简单,(up_x=up_{fa_x}+deg_x-2+[x=st]),其中(st)表示起点,因为此时管理员还需要擦掉到(x)的一条边
考虑(f_x)的转移,站在老鼠的角度,假设(yin son_x),那么它肯定会选择一个(max f_y)的(y)钻进去,由于这里是管理员先操作,因此老鼠只有(operatorname{secondmax} f_y)的(y)可以钻
得到(f_x)的转移,(f_x=operatorname{secondmax}_{yin son_x} f_y+deg_x-1)
然后刚开始naive地以为老鼠开始只能向下走,后来WA了好久才发现老鼠可以先向上走几步然后再钻进某棵子树
刚开始想通过枚举老鼠从哪个点钻进子树,结果发现在老鼠向上的过程中管理员的操作就完全不可知,非常难处理
遂转换思路,我们发现答案显然具有可二分性,于是我们把原问题转化成判定性问题
考虑此时管理员剩下的操作步数(t),老鼠当前在点(x),枚举(yin son_x),此时若(up_x+f_y>t)老鼠肯定会选择一头钻进去
我们再维护一个变量(left)表示管理员在老鼠向上跳的过程中腾出的操作数,如果(up_x+f_y>t)且(left=0)那么就是老鼠的胜利,否则管理员可以通过(left-=1)来提前堵住这条边
综上,这道题就解决了,复杂度(O(nlog n))
#include<cstdio>
#include<iostream>
#include<cctype>
#define RI register int
#define CI const int&
#define Tp template <typename T>
using namespace std;
const int N=1000005;
struct edge
{
int to,nxt;
}e[N<<1]; int n,head[N],x,y,rt,st,cnt,ans,anc[N],f[N],up[N],deg[N]; bool tag[N];
class FileInputOutput
{
private:
static const int S=1<<21;
#define tc() ((A==B&&(B=(A=Fin)+fread(Fin,1,S,stdin)),A==B)?EOF:*A++)
char Fin[S],*A,*B;
public:
Tp inline void read(T& x)
{
x=0; char ch; int flag=1; while (!isdigit(ch=tc())) if (ch=='-') flag=-1;
while (x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc())); x*=flag;
}
#undef tc
}F;
inline void addedge(CI x,CI y)
{
e[++cnt]=(edge){y,head[x]}; head[x]=cnt; ++deg[x];
e[++cnt]=(edge){x,head[y]}; head[y]=cnt; ++deg[y];
}
#define to e[i].to
inline void DFS(CI now,CI fa=0)
{
int mx=0,smx=0; anc[now]=fa;
if (now!=rt) up[now]=up[fa]+deg[now]-2+(now==st);
for (RI i=head[now];i;i=e[i].nxt) if (to!=fa)
{
DFS(to,now); if (f[to]>mx) smx=mx,mx=f[to];
else if (f[to]>smx) smx=f[to];
}
f[now]=smx+deg[now]-1;
}
inline bool check(int t)
{
int left=1; for (RI now=st,i;now!=rt;now=anc[now],++left)
{
int dlt=0; for (i=head[now];i;i=e[i].nxt)
if (to!=anc[now]&&!tag[to]&&up[now]+f[to]>t)
if (!left) return 0; else ++dlt,--left; t-=dlt;
}
return t>=0;
}
#undef to
int main()
{
RI i; F.read(n); F.read(rt); F.read(st); if (st==rt) return puts("0"),0;
for (i=1;i<n;++i) F.read(x),F.read(y),addedge(x,y);
for (DFS(rt),i=st;i;i=anc[i]) tag[i]=1;
int l=0,r=n-1,mid; while (l<=r)
if (check(mid=l+r>>1)) ans=mid,r=mid-1; else l=mid+1;
return printf("%d",ans),0;
}