hdu4317 Unfair Nim 二进制状态压缩

题意:

两个人玩Nim游戏,Alice和Bob 。Alice先一步。 Alice允许Bob 在任意一堆石子上 加任意的石头,使得Bob赢。问Bob 最少加多少石子。如果Bob不可能让自己赢,就输出impossible。

乍一看貌似是博弈论,仔细分析一下其实是障眼法。。。 

将n堆棋子上的数都看成是2进制。Nim游戏的规则是:每个人只允许在其中一堆中拿任意的石子。 假设n个二进制数 的每一位都有偶数个1(比如个位:将第一个数的个位和第二个数的个位...第n个数的个位看成一列,即其中1的个数为偶数个)  ,也就是说每一位都异或为0。那么第一个人取得任意的石子,都必将会使其中某些位不再异或为0(正由于只允许在一堆上取石子造成的),那么第二个人总可以再另外一堆石子上取对应的石子使得每一位再次恢复异或为0。 按照这样下去,石子数量逐渐减少,所以最后的石子肯定是由第二个人取得,因为第一个人取之后所有位必定不能全异或为0。

到这里题目的真正目的就出来了:对于n个二进制数中的某一个数,对它加上一个数后,能否使得每一位都异或为0。如果能,输出加上的数中最小的那一个。

石子堆数为个位数的数量级,所以用状态压缩是不会te的。

将n个数的第k位 排成一列,作为该题的状态。从低位到高位进行dp。

dp[ i ][ j ]: 第i 位,上一位进位状态为 j 时 满足前 i 位 异或为0,所需添加的最小石子数。

s[ i ] 初始第 i 位中的二进制数

a[ ] 存初始每一堆的数

t[m ] 预处理 某一位状态为m 时,1的个数。

解释几个位运算代表的意思:

 j & s[r] 进位和初始状态相加后产生的进位

s[ r ] ^ j 相加进位之后剩下的数

k ^ tmp  某一位需要的进位

判断状态是否可以转移时有3个条件:

1.规定了k的每一位进位必须大于或等于tmp的每一位 :(k&tmp)==tmp

2.规定了如果要继续进位成k则要求s[i]^j的那一位至少为1 :((s[r]^j)&(k^tmp))==(k^tmp)

3.规定该进位下的1的个数为偶数,或者为奇数但前提是个数少于n,因为可以任意补一个1使得为偶数,不影响进位 :((t[(s[r]^j)^(k^tmp)]&1)==0||t[(s[r]^j)^(k^tmp)]<N)


状态转移方程:

dp[r][k]=min(dp[r][k],dp[r-1][j]+(t[k^tmp]+(t[(s[r]^j)^(k^tmp)]&1))*(1<<(r-1)))


需要注意一下位运算的优先级,以免括号打错。

具体上代码了。。。


#include<iostream>
#include<cstdio>
#include<string.h>
#include<algorithm>
using namespace std;
const int M=1<<11;
const int MAX=0x1f1f1f1f;
int s[25],dp[25][M];//dp[i][j]:第i列,上一列进位状态为j时的最小加石子数
int t[M];//状态为M的二进制数中1 的个数
int a[11];//初始石子数

int main()
{
    int N,i,j,k,len,tmp,r;
    for(i=0;i<M;i++)
    {
        tmp=0;
        j=i;
        while(j!=0)
        {
            tmp+=(j&1);
            j=j>>1;
        }
        t[i]=tmp;
    }
    while(scanf("%d",&N)!=EOF)
    {
        memset(s,0,sizeof(s));;
        for(i=0;i<N;i++)
        {
            scanf("%d",&a[i]);
        }
        if(N<2)
        {
            printf("impossible\n");
            continue;
        }
        for(i=1;i<=22;i++)
        {
            for(j=0;j<N;j++)
            {
                if(a[j]&(1<<(i-1)))
                {
                    s[i]|=(1<<j);//二进制加
                }
            }
            if(s[i]!=0)
            len=i+1;
        }
        memset(dp,MAX,sizeof(dp));
        dp[0][0]=0;
        for(r=1;r<=len;r++)
        {
            for(j=0;j<(1<<N);j++)
            {
                if(dp[r-1][j]<MAX)
                {
                    tmp=j&s[r];//由于上次进位而产生的初始进位
                    for(k=tmp;k<(1<<N);k++)
                    {

                        if((k&tmp)==tmp&&/*规定了k的每一位进位必须大于或等于tmp的每一位*/
                           ((s[r]^j)&(k^tmp))==(k^tmp)&&/*规定了如果要继续进位成k则要求s[i]^j的那一位至少为1*/
                           ((t[(s[r]^j)^(k^tmp)]&1)==0||t[(s[r]^j)^(k^tmp)]<N))/*规定该进位下的1的个数为偶数,或者
                           为奇数但前提是个数少于n,因为可以任意补一个1使得为偶数,不影响进位*/
                           //前2个判断的共同依据:如果当前位进位后还是为0则不可能再进位,因为当前位加1也只为1,加2则可以归纳到高一位的判断中
                           {
                               dp[r][k]=min(dp[r][k],dp[r-1][j]+(t[k^tmp]+(t[(s[r]^j)^(k^tmp)]&1))*(1<<(r-1)));
                           }
                    }
                }
            }
        }
        int Min=MAX;
        for(j=0;j<(1<<N);j++)
        {
            if((s[j]&1)==0)
                Min=min(Min,dp[len][j]);
        }
        printf("%d\n",Min);
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值