某場模擬賽 博弈(樹形DP+二分+貪心)

題目大意:
一棵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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章