博弈的暴力

总起

很多比赛中都有博弈题,这些题或难或简单,而且具有打暴力的能力一定很吃香。
大概有这么一些题目吧。

预备

通常打博弈暴力,弄清楚最基本的一点:双方都是最优策略.
简单的说,A要B最差,B要A最差。
大概这里有几种题型吧。
①2个人在树上操作。
解题的关键:先手要尽量逼后手处于必败态.
②两个人下棋。
解题的关键:A要两人得分之差尽量高,B要两人得分之差尽量低.
③两人轮流拿石子。
解题的关键:设正确的SG函数.

进入正题

T1.JZOJ 5637. 一双木棋
题目大意:2个人在一个棋盘上下棋,一方在一个位置能够落子当且仅当这个位置上、左全都是棋子。 A (i,j) 下棋,得分为 A[i,j] B (i,j) 下棋,得分为 B[i,j]
那么,如何打博弈暴力呢?
考虑在递归地时候设几个参数。下面这种设法是通用的:
①现在是谁操作。②此局面该去其子节点的max还是min。③当前的局面是什么。
根据这个其实已经可以拿到部分分了。

代码

int Max(int x,int y){return x>y?x:y;}
int Min(int x,int y){return x<y?x:y;}
int dg(int x,int y,int z,int gm){
    if(y>n*m)return z;
    int i,j,k,l;bool p;
    if(x){
        int mx=-2147483647;
        fo(i,1,n)fo(j,1,m)if((gm&_2[pos[i][j]])==0){
            p=1;
            fo(k,1,i){
                fo(l,1,j){
                    if(k==i&&l==j)continue;
                    if((gm&_2[pos[k][l]])==0){
                        p=0;
                        break;
                    }
                }
                if(!p)break;
            }
            if(p){
                mx=Max(mx,dg(0,y+1,z+A[i][j],gm|_2[pos[i][j]]));
            }
        }
        return mx;
    }else{
        int mi=2147483647;
        fo(i,1,n)fo(j,1,m)if((gm&_2[pos[i][j]])==0){
            p=1;
            fo(k,1,i){
                fo(l,1,j){
                    if(k==i&&l==j)continue;
                    if((gm&_2[pos[k][l]])==0){
                        p=0;
                        break;
                    }
                }
                if(!p)break;
            }
            if(p){
                mi=Min(mi,dg(1,y+1,z-B[i][j],gm|_2[pos[i][j]]));
            }
        }
        return mi;
    }
}

剪枝: αβ 剪枝。
假设现在的操作是取 max ,现在已经有一个目前的最优解 α ,如果它的某个孙子节点的 α <当前节点的 α ,那么现在这个儿子点就不用再走了。
min 同理。
T2.JZOJ 5679. 山景城
有一棵树,A要从 S 号节点走到根节点 T ,每个回合中,A必须走一步,当且仅当连着A所在的点的某一条边没有被标记,A可以往这个方向走。走完后这条边被立即标记。
B可以消除掉某些边的标记(可以一次性选择多条),并且可以炸毁1条边。
数据范围:20% n10 ,另20%,保证存在一条s到t的边。
明确几个要点。
考虑能不能够堵住A,让B做完所有的操作 ,再让A走。
对于 s t 相邻的情况,显然让A走到叶子节点 y ,这个时候 y T 的路径全被堵死了。
再考虑如何一定让B走回 T ,并且耗时最短。
显然当A准备从 z 走回去 x 的时候,B先将所有通向 x 的 不在 y T 的路径 的儿子的边全部炸了,再逼A走到 x 。记录这个花费为 w[z]
最后扫的时候,通常走当前节点的第2优的边。如果该节点只有一个儿子,注意特判。
T3:JZOJ 5693. 对战
有一棵树。树上的点有2种颜色,两个人轮流选择一个白色的点,将其到根的路径上的点
全部染黑。不能操作者输。
初始树上的节点并非全白。
10%, n10
考虑 sg 函数是怎么来的,它是基于如果存在一个后继状态为必败态,则这个为必胜态。所以在暴力的时候,直接暴力每个操作,算出这个状态是否是必胜态即可。
即:暴力+记忆化,如果有任意子状态是必败态,则这个是必胜态。
由于n≤10,所以拿二进制状态存一下就好了。
f[] 的初始值为-1.

bool check(){
    int i,S=0;
    fo(i,1,n)if(c[i]==0)S|=_2[i];
    if(S==0)return 0;
    if(~f[S])return f[S];
    fo(i,1,n)if(c[i]==0){
        bomb(i);//操作
        if(!check()){
           rebuild(i);//回步
           return f[S]=1;
        }
        rebuild(i);
    }
    return f[S]=0;
}

40%, n1000
(数据不保证随机)
审一下题目的条件。
①选的点必须为白色。②若一个白色点被选,则从根到它的路径的点不能选。③初始树上的节点并非全白。
分析:
结合条件③,需要保证只管白色的点。
综合条件①②,若一条路径上的点全被染黑,则与这条路径上的有边相连的 路径外的点,全都是独立开来的。
在这道题目上,定义一个游戏为:目前只考虑 x 的子树,且一开始要选择 x 节点。
在想题的时候,需要看出来这道题目的类型:如果将一个游戏看作节点,则会出现一棵树。(即游戏与游戏之间有父子关系)
所以要考虑 sg 函数。
考虑一个游戏的后继状态,则这个游戏的 sg 函数为游戏的后继状态的 sg 函数的 mex
一个后继状态的 sg 函数,是独立开来的那些子游戏的 sg 的异或和。
然后暴力就好了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 100010
#define LL long long
#define P(a) putchar(a)
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
struct note{
    int to,next;
};note edge[N*2];
int tot,head[N];
int c[N];
int i,j,k,l,n,m;
int nim;
int fa[N],Fa[N],ans[N];
int o[N],cnt[N],CNT;
int sg[N];
int u,v,X;
int read(){
    int fh=1,rs=0;char ch;
    while((ch<'0'||ch>'9')&&(ch^'-'))ch=getchar();
    if(ch=='-')fh=-1,ch=getchar();
    while(ch>='0'&&ch<='9')rs=(rs<<3)+(rs<<1)+(ch^'0'),ch=getchar();
    return fh*rs;
}
void write(int x){
    if(x>9)write(x/10);
    P(x%10+'0');
}
void lb(int x,int y){
    edge[++tot].to=y;
    edge[tot].next=head[x];
    head[x]=tot;
}
void dfs(int x,int y){
    if(!c[x])Fa[x]=y;
    int i;
    for(i=head[x];i;i=edge[i].next)
        if(edge[i].to^fa[x]){
            fa[edge[i].to]=x;
            if(c[x])dfs(edge[i].to,y);
               else dfs(edge[i].to,x);
        }
}
void check(int x){
    int i;
    for(i=head[x];i;i=edge[i].next)k^=sg[edge[i].to];
    cnt[k]=CNT;
    for(i=head[x];i;i=edge[i].next){
        k^=sg[edge[i].to];
        check(edge[i].to);
        k^=sg[edge[i].to];
    }
    for(i=head[x];i;i=edge[i].next)k^=sg[edge[i].to];
}
void dg2(int x){
    int i;
    for(i=head[x];i;i=edge[i].next)k^=sg[edge[i].to];
    if(!k)ans[++ans[0]]=x;
    for(i=head[x];i;i=edge[i].next){
        k^=sg[edge[i].to];
        dg2(edge[i].to);
        k^=sg[edge[i].to];
    }
    for(i=head[x];i;i=edge[i].next)k^=sg[edge[i].to];
}
void dg(int x){
    int i;
    if(!o[x]){
        sg[x]=1;
        return;
    }
    for(i=head[x];i;i=edge[i].next)
        if(edge[i].to^Fa[x])
            dg(edge[i].to);
    X=x;
    CNT++;
    k=0;
    check(x);
    for(sg[x]=0;cnt[sg[x]]==CNT;sg[x]++);
}
int main(){
    freopen("combat.in","r",stdin);
    freopen("combat.out","w",stdout);
    n=read();
    fo(i,1,n)c[i]=read();
    fo(i,1,n-1){
        u=read();v=read();
        lb(u,v);lb(v,u);
    }
    dfs(1,0);
    memset(edge,0,sizeof(edge));
    memset(head,0,sizeof(head));
    tot=0;
    fo(i,2,n)if(Fa[i]){
        lb(Fa[i],i);
        o[Fa[i]]++;
    }
    nim=0;
    fo(i,1,n)if(!c[i]&&!Fa[i]){
        dg(i);
        nim^=sg[i];
    }
    if(!nim)printf("-1");
    else{
        fo(i,1,n)if(!Fa[i]&&!c[i])lb(0,i);
        k=0;
        dg2(0);
        sort(ans+1,ans+ans[0]+1);
        fo(i,1,ans[0])if(ans[i])write(ans[i]),P('\n');
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/huangjingyuan107/article/details/80086255