题目大意:
一棵n个节点的树,初始时s节点有一个棋子,两个人A、B轮流进行操作,规则如下:
①A先手,A可以选择不进行操作,或选择操作,即选择删除一条边或清除一条边上的标记。
②B后手,每次B会选择与棋子所在节点相邻的一条没有标记的边,将棋子移动到边的另一端,并在边上做标记。如果与棋子所在节点相邻的边都有标记则B不操作(如果存在与棋子所在节点相邻的未标记的边,则B必须移动棋子)。
③棋子移动到t节点时游戏结束。
A的目标是最小化自己的操作数,B的目标是最大化A的操作数,双方都采用最优策略,问最终A的操作数。
题解:
思路比较神奇。
不难发现,最优策略下,棋子会首先被移动到一个叶节点,之后不再移动,直到A不断删边,只留下一条这个叶节点到t的一条路径时,A再清除路径上的标记,让棋子移动到t节点。
先考虑一种简单的情况:s和t直接有边相连。将t作为树根,第一轮时A会断掉s下使自己操作次数最大的子树,然后B把棋子移动到使A操作次数次大的子节点。这个就可以用一个树形DP解决了。设w[i]表示棋子先被移动到以i为根的子树中再移动到t时A的最小操作次数,转移比较容易(详见代码)。
当s到t没有边相连时,棋子有可能先向深度更小的点走一段,再走进某一棵子树,变成刚刚的问题。由于棋子移向深度更小的点,dp的状态会难以定义,就不能直接DP了。但是可以二分答案,先类似上面所说地dp出w[i],然后贪心地把使操作数大于mid的子树断开,检验是否合法(详见代码)。
感觉这个二分答案比较神奇(⊙▽⊙),check()的思路比较巧妙 φ(>ω<*)
code
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define N 1000005
using namespace std;
inline int read()
{
char c=getchar(); int num=0,f=1;
while (c<'0'||c>'9') { if (c=='-') f=-1; c=getchar(); }
while (c<='9'&&c>='0') { num=num*10+c-'0'; c=getchar(); }
return num*f;
}
struct edge{
int to,ne; bool del;
}e[N<<1];
int n,s,t,tot,head[N],son[N],sta[N],top,road[N],dp[N],w[N],fa[N];
bool mark[N];
void push(int x,int y)
{
e[++tot].to=y; e[tot].ne=head[x]; head[x]=tot;
e[++tot].to=x; e[tot].ne=head[y]; head[y]=tot;
}
void bfs(int now)
{
int tou=1; top=1; sta[tou]=now;
while (tou<=top)
{
now=sta[tou++];
for (int i=head[now];i;i=e[i].ne)
{
int v=e[i].to; if (v==fa[now]) continue;
fa[v]=now; son[now]++; sta[++top]=v;
}
}
}
inline bool check(int mid) //check很妙啊~
{
int cnt=0,tim=0;
for(int i=1;i<=road[0];i++)
{
int now=road[i],tmp=0; tim++;
for (int j=head[now];j;j=e[j].ne)
{
int v=e[j].to; if (v==road[i-1]||v==road[i+1]||v==t) continue;
if (w[v]+cnt>mid) tmp++,tim--;
}
cnt+=tmp;
if (tim<0||cnt>mid) return false;
}
return true;
}
int main()
{
n=read(); t=read(); s=read();
for (int i=1;i<n;i++) push(read(),read());
bfs(t);
for (int i=s;i!=t;i=fa[i]) road[++road[0]]=i,mark[i]=true;
dp[road[road[0]]]=0;
for (int i=road[0]-1;i>=1;i--) dp[road[i]]=dp[road[i+1]]+son[road[i+1]]-1;
for (int i=1;i<=top;i++)
{
int now=sta[i]; if (mark[now]) continue;
if (mark[fa[now]]&&fa[now]!=s) dp[now]=dp[fa[now]]+son[fa[now]]-1;
else dp[now]=dp[fa[now]]+son[fa[now]];
}
for (int i=top;i>=1;i--)
{
int now=sta[i]; if (now==t||mark[now]) continue;
if (!son[now]) w[now]=dp[now];
else if (son[now]==1) w[now]=dp[now]+1;
else
{
int mx=-1,mx2=-1;
for (int j=head[now];j;j=e[j].ne)
{
int v=e[j].to; if (v==fa[now]) continue;
if (mx==-1||w[v]>w[mx]) mx2=mx,mx=v;
else if (mx2==-1||w[v]>w[mx2]) mx2=v;
}
w[now]=w[mx2];
}
}
int l=0,r=n,ans;
while (l<=r)
{
int mid=l+r>>1;
if (check(mid)) ans=mid,r=mid-1;
else l=mid+1;
}
printf("%d",ans);
fclose(stdin);
fclose(stdout);
return 0;
}