P4654 [CEOI2017] Mousetrap
为了方便,以目标为根,向深度浅的位置走为“向上走”,否则为“向下走”。
考虑到老鼠一旦开始向下走,它就一定会一直向下,直到走到叶子或者唯一向下的路被堵住了,此时只需要封住所有岔路并擦干即可。
设 \(f_i\) 表示将老鼠从 \(i\) 赶回 \(i\) 的最优解。
首先,\(i\) 的所有子树必须被封上/擦干一次,否则老鼠蹿上来后又回进入其他子树。
由于博弈,所以老鼠会选择子树中 \(f\) 大的走,由于只能封一个,所以会选次大的走(不存在的话路直接就给堵死了,为 \(0\))
\(f_u=\operatorname{2ndmax}\limits_{v\in son_u} f_v+\deg u-1\)
考虑设 \(h(u)\) 表示 \(u\rightarrow rt\) 的所有岔边,\(g_u\) 表示从进入 \(u\) 到回到根的最优解。
由于如果存在“向上走”,则有一条边会不用堵。
\(g_u=h(fa_u)+f_u+[fa_u=m]\)
接下来的问题是不清楚老鼠会向上跳到哪里,所以考虑二分答案,转变为输赢博弈(小于等于管理员胜,否则老鼠胜)。
由于堵住的路不能疏通,所以我们只能堵住所有岔路,无法阻止老鼠上跳。
考虑在老鼠上跳的过程中,封住所有可能导致失败的子树。如果太多导致次数不够或者手速太慢就会失败。
有一个细节,就是 \(u\) 的某个儿子被封上了,不能直接让 \(x\) 减去一,必须等到所有子树做完后再统一减。
原因在于如果老鼠进入了 \(u\) 的其它子树 \(v\) ,那么由于回到根时 \(u\) 的所有儿子都应该被堵上,这个贡献被 \(g_v\) 多算了一次 ,所以 \(x\) 减后 \(g_v\) 也会减同样的值,相当于没减。这就是为什么 check
中会出现 tmp
。
View Code
#include <cstdio>
#include <algorithm>
#include <cctype>
using namespace std;
char buf[1<<14],*p1=buf,*p2=buf;
#define GetC() ((p1==p2)&&(p2=(p1=buf)+fread(buf,1,1<<14,stdin),p1==p2)?EOF:*p1++)
struct Ios{}io;
template <typename _tp>
Ios &operator >>(Ios &in,_tp &x){
x=0;int w=0;char c=GetC();
for(;!isdigit(c);w|=c=='-',c=GetC());
for(;isdigit(c);x=x*10+(c^'0'),c=GetC());
if(w) x=-x;
return in;
}
const int N=1e6+5;
int n,t,m;
struct EDGE{ int nxt,to; }e[N<<1];
int head[N],tot;
void add(int from,int to){
e[++tot].nxt=head[from];
e[tot].to=to;
head[from]=tot;
}
int dp[N],deg[N],h[N],f[N],g[N];
int st[N],top;
void dfs(int u,int fa){
f[u]=fa;
dp[u]=0;
int mx1=0,mx2=0;
if(fa) h[u]=h[fa]+deg[u]-1-(fa!=0);
else h[u]=0;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(v==fa) continue ;
dfs(v,u);
if(dp[v]>=mx1) mx2=mx1,mx1=dp[v];
else mx2=max(mx2,dp[v]);
}
dp[u]=mx2+deg[u]-1;
if(fa==0) dp[u]=0;
}
bool check(int x){
int sum=0,tmp=0;
for(int i=1;i<top;++i){
int u=st[i];
tmp=0;
for(int _=head[u];_;_=e[_].nxt){
int v=e[_].to;
if(v==st[i+1]||v==st[i-1]) continue ;
if(dp[v]>x) ++tmp;
}
sum+=tmp;x-=tmp;
if(x<0||sum>i) return false;
}
return true;
}
int main(){
io>>n>>t>>m;
if(t==m){
puts("0");
return 0;
}
for(int i=1;i<n;++i){
int u,v;io>>u>>v;
add(u,v);add(v,u);
++deg[u];++deg[v];
}
dfs(t,0);
for(int i=1;i<=n;++i){
if(i!=t){
dp[i]+=h[f[i]]+(f[i]==m);
}
}
for(int i=m;i;i=f[i]) st[++top]=i;
int l=0,r=2*n;
while(l<=r){
int mid=(l+r)>>1;
if(check(mid)) r=mid-1;
else l=mid+1;
}
printf("%d\n",l);
return 0;
}