总起
很多比赛中都有博弈题,这些题或难或简单,而且具有打暴力的能力一定很吃香。
大概有这么一些题目吧。
预备
通常打博弈暴力,弄清楚最基本的一点:双方都是最优策略.
简单的说,A要B最差,B要A最差。
大概这里有几种题型吧。
①2个人在树上操作。
解题的关键:先手要尽量逼后手处于必败态.
②两个人下棋。
解题的关键:A要两人得分之差尽量高,B要两人得分之差尽量低.
③两人轮流拿石子。
解题的关键:设正确的SG函数.
进入正题
T1.JZOJ 5637. 一双木棋
题目大意:2个人在一个棋盘上下棋,一方在一个位置能够落子当且仅当这个位置上、左全都是棋子。
那么,如何打博弈暴力呢?
考虑在递归地时候设几个参数。下面这种设法是通用的:
①现在是谁操作。②此局面该去其子节点的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;
}
}
剪枝:
假设现在的操作是取
取
T2.JZOJ 5679. 山景城
有一棵树,A要从
B可以消除掉某些边的标记(可以一次性选择多条),并且可以炸毁1条边。
数据范围:20%
明确几个要点。
考虑能不能够堵住A,让B做完所有的操作 ,再让A走。
对于
再考虑如何一定让B走回
显然当A准备从
最后扫的时候,通常走当前节点的第2优的边。如果该节点只有一个儿子,注意特判。
T3:JZOJ 5693. 对战
有一棵树。树上的点有2种颜色,两个人轮流选择一个白色的点,将其到根的路径上的点
全部染黑。不能操作者输。
初始树上的节点并非全白。
10%,
考虑
即:暴力+记忆化,如果有任意子状态是必败态,则这个是必胜态。
由于n≤10,所以拿二进制状态存一下就好了。
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%,
(数据不保证随机)
审一下题目的条件。
①选的点必须为白色。②若一个白色点被选,则从根到它的路径的点不能选。③初始树上的节点并非全白。
分析:
结合条件③,需要保证只管白色的点。
综合条件①②,若一条路径上的点全被染黑,则与这条路径上的有边相连的 路径外的点,全都是独立开来的。
在这道题目上,定义一个游戏为:目前只考虑
在想题的时候,需要看出来这道题目的类型:如果将一个游戏看作节点,则会出现一棵树。(即游戏与游戏之间有父子关系)
所以要考虑
考虑一个游戏的后继状态,则这个游戏的
一个后继状态的
然后暴力就好了。
#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;
}