Comet OJ - Contest #1 C 复读游戏(状态压缩)

题意

https://www.cometoj.com/contest/35/problem/C?problem_id=1498

思路

这题要用到一种比较小众的状压方法(没见过的话可能一时比较难想到)。

首先观察题面,发现可以把一个人有另一个人没有的点数都视作同一种(转化一),然后点数之间也可以任意转化(转化二),不影响结果,经过如上转化,可以将任意情况转化为两方各有一些相同点数的手牌,然后不同点数的手牌只有一种或没有。

(感觉说的好乱啊,举个栗子吧...

\[\begin{array}{} &2,3,5,6,6|1,2,5,5,7\\ \to&2,5,X,X,X|2,5,5,Y,Y\text{(转化一)}\\ \to&1,2,X,X,X|1,2,2,Y,Y\text{(转化二)}\\ \to&1,2,3,3,3|1,2,2,4,4\text{(转化二)}\\ \end{array} \]

然后就是状压的部分,由于两人最多只能有 \(8\) 张手牌。而手牌之间只有相同不相同才有影响,于是把手牌变成 \(01\) 序列,即相邻不同的用 \(01\) 相区别。

(感觉还是不清楚啊,继续举栗子吧...

\(1,2,3,3,3 \to 10111\)

$ 1,2,2,4,4 \to 10011$

当然为了表示有没有对方没有对牌,还需另外记一个布尔值。

最后,任何时刻不得打出上次打出的牌,所以再记一个整数表示上一次打出的手牌标号,为了方便起见,这个标号是相对于本方而言的。然后如果因为是第一回合,或者对方打了本方没有的牌等情况,没有这个限制的话,这个整数就记为 \(8\) (因为本方手牌最多从 \(0\) 标号到 \(7\) 嘛)。

细节还是很多的,据说出题人和验题人的代码也都很长,我还是赛后膜改一天改出了下面这个代码,感触最深的就是把位操作封函数后会好写很多。

代码

#include<bits/stdc++.h>
#define FOR(i,x,y) for(int i=(x),i##END=(y);i<=i##END;++i)
#define DOR(i,x,y) for(int i=(x),i##END=(y);i>=i##END;--i)
template<typename T,typename _T>inline bool chk_min(T &x,const _T y){return y<x?x=y,1:0;}
template<typename T,typename _T>inline bool chk_max(T &x,const _T y){return x<y?x=y,1:0;}
typedef long long ll;
int dp[(1<<8)+5][2][(1<<8)+5][2][9];	//0:lose  1:draw  2:win
int bin[(1<<8)+5];							//dp[a][b][c][d][e]
int T;										//a为本方手牌
											//b为本方有没有对方没有的牌
inline int bit_take(int B,int l,int r)		//c为对方手牌
{											//d为对方有没有本方没有的牌
	return B&(((1<<(r+1))-1)^((1<<l)-1));	//e为上一次出的手牌标号(相对本方而言)
}
inline int bit_erase(int B,int x)
{
	return bit_take(B,0,x-1)|(bit_take(B,x+1,bin[B])>>1);
}
inline int bit_reverse(int B,int l,int r)
{
	return B^(((1<<(r+1))-1)^((1<<l)-1));
}
inline int bit_swapping(int B,int l1,int r1,int r2)
{
	int B1=bit_take(B,l1,r1),B2=bit_take(B,r1+1,r2);
	return (B^B1^B2)|(B1<<(r2-r1))|(B2>>(r1-l1+1));
}

int get_dp(int A,bool a,int B,int b,int las)
{
	int &res=dp[A][a][B][b][las];
	if(~res)return res;
	else if(B==0)
	{
		if(las==8)return res=1;
		else return res=0;
	}
	res=0;
	int cnta=-1,cntb=-1;
	int ra[9],rb[9];
	DOR(i,bin[A],0)if(i==bin[A]||((A>>i&1)!=(A>>(i+1)&1)))ra[++cnta]=i;
	DOR(i,bin[B],0)if(i==bin[B]||((B>>i&1)!=(B>>(i+1)&1)))rb[++cntb]=i;
	ra[cnta+1]=rb[cntb+1]=-1;
	FOR(i,0,cnta)
	{
		int l=ra[i+1]+1,r=ra[i];
		if(i!=las)
		{
			int nA=bit_erase(A,r),na=a,nB=B,nb=b,nlas;
			if(l==r)
			{
				nA=bit_reverse(nA,0,r-1);
				if(a&&i==cnta)na=0,nlas=8;
				else if(i==cntb)nb=1,nlas=cntb;
				else
				{
					int x=rb[cntb+1]+1,y=rb[i+1],z=rb[i];
					nB=bit_reverse(nB,x,y);
					if((cntb-b-i)&1)nB=bit_reverse(nB,y+1,z);
					nB=bit_swapping(nB,x,y,z);
					if(b)nlas=8;
					else nlas=cntb,nb=1;
				}
			}
			else
			{
				if(a&&i==cnta)nlas=8;
				else nlas=i;
			}
			chk_max(res,2-get_dp(nB,nb,nA,na,nlas));
		}
	}
	return res;
}

void solve()
{
	int n;
	int A[25]={0},B[25]={0};
	scanf("%d",&n);
	FOR(i,1,n)
	{
		int x;
		scanf("%d",&x);
		A[x]++;
	}
	FOR(i,1,n)
	{
		int x;
		scanf("%d",&x);
		B[x]++;
	}
	int X=0,Y=0,x=0,y=0;
	bool cur=1;
	FOR(i,1,20)
	{
		if(A[i]&&!B[i])x+=A[i];
		else if(!A[i]&&B[i])y+=B[i];
		else if(A[i]&&B[i])
		{
			FOR(j,1,A[i])X=(X<<1)|cur;
			FOR(j,1,B[i])Y=(Y<<1)|cur;
			cur^=1;
		}
	}
	FOR(i,1,x)X=(X<<1)|cur;
	FOR(i,1,y)Y=(Y<<1)|cur;				//转化手牌
	int res=get_dp(X,x!=0,Y,y!=0,8);
	if(res==0)puts("dreamoon wins");
	else if(res==1)puts("Draw");
	else if(res==2)puts("AA wins");
}

int main()
{
	bin[1]=0;FOR(i,2,1<<8)bin[i]=bin[i>>1]+1;
	memset(dp,-1,sizeof(dp));
	int T;
	scanf("%d",&T);
	while(T--)solve();
	return 0;
}
posted @ 2019-07-05 10:51  Paulliant  阅读(77)  评论(0编辑  收藏