題目大意:
一棵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;
}