某场模拟赛 博弈(树形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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章