hdu - 4317 - Unfair Nim - 状态dp

我一直觉得状压就是暴搜,其实这题也就是暴搜。。。http://acm.hdu.edu.cn/showproblem.php?pid=4317

题意:给你n个数,你可以在每个数加上一个值,使得最后的n个数的异或值为0,当然要求加的数的和最小,这之前有个小小的博弈障眼法呵呵。

N非常小,状态压缩自然从N入手,用二进制表示这N堆的所有选择。
根据异或的性质,只要异或的这些数字对应二进制相同的位上有偶数个1,则这位异或的结果就为0。
dp[i][j]:i代表由低到高的第几位,j是第i位压缩过后的状态,表示第i位的进位。dp[i][j]代表达到这样的状态所需的最小石子数。
在这样的定义下,如果i能达到的最高位的进位j有偶数个1,则说明这是答案的一种选择,只要选取这些取值中最小的就可以了。

下面考虑状态之间的关系。
i状态只可能由i-1的状态得来。我们用c[i]记录每一位石子数量的初始状态,如果第k个石子的第i位为1,则c[i]的第k位就为1。
我们用num[x]表示x中1的个数,isEven[x]表示x中1的个数是否为偶数个。
对于一个j,由i-1状态下的k得来,k要想到达j,需要满足以下条件:

1. 保证k可以到达j

如果c[i]和k的某一位都为1,则在这一位一定产生了进位,j在这一位也必须为1,所以有一个约束条件:((j | (c[i] & k)) == j)

2. 保证i位的1的数量为偶数
由于j是状态c[i]与k二进制加法得到的结果再进位,c[i] ^ k所得的数对应位,
如果为0,说明需要加上2来产生进位,结果还是0。
如果为1,说明需要加上1来产生进位,结果变为0。
所以:
如果j对应位为0:什么也不用做。
如果j对应位为1,c[i] ^ k对应位为0:需要加2,还是0。
如果j对应位为1,c[i] ^ k对应位为1:需要加1变0。
最终,只要本来就是偶数,或还有0位可以让我们填上1个石子,我们就一定可以获得偶数的情况,
因为j有1的位产生进位后一定为0,所以:
本来就是偶数:isEven[(~j) & (c[i] ^ k)],除去j有1的位的1后剩下的1的数量为偶数
还有空闲的0位:j || num[(c[i] ^ k) | j] < n,因为只要有j就有0的存在,或除去有j有1位的地方有0存在(j == 1, i ^ k == 0   ||    j == 0, i ^ k == 0)

总的判定为:(isEven[(~j) & (c[i] ^ k)] || j || num[(c[i] ^ k) | j] < n)

3. 由状态k到达状态j
根据上面所说,我们找到了满足条件的k,接下来要考虑需要石子的数量。
首先是已经有进位的位,c[i]和k对应位都为1,(c[i] & k)这样的位无需考虑。
然后考虑是否要补一个石子来满足偶数条件,有就加1。
接下来是c[i] ^ k对应位为1的情况,也就是(j & (c[i] ^ k))中1的数量,需要在这一位加上同等数量的石子。
最后是c[i] ^ k对应位为0的情况,也就是(j ^ (j & (c[i] ^ k)))中1的数量去掉(c[i] & k)中1的数量,需要在这一位上加上二倍数量的石子。
由于是第i-1位,需要的石子的数量要乘上1<<(i-1)。//感觉这个是非常神的。 这时要求j为1, c[i] ^ k为0,并且要求c[i] & k被忽略。

综上所述:
dp[i][j] = min(dp[i][j], dp[i-1][k] + (!isEven[(~j) & (c[i] ^ k)] + num[j & (c[i] ^ k)] + ((num[j ^ (j & (c[i] ^ k))] - num[c[i] & k]) << 1)) * (1 << (i-1)));

边界条件:
dp[0][0] = 0,其它值为INF。

对于多堆情况,一定能找到一种方法使游戏成立,只有一堆的时候才会出现必输的情况。
当n==1且数量不为0时,这是不可能满足条件。

#include <cstdio>
#include <cstring>
const int MAXN = 15;
const int MAXM = 25;
const int INF = 1000000000;

int n, m, a[MAXN];
int c[MAXM], dp[MAXM][1<<MAXN];
int num[1<<MAXN];
bool isEven[1<<MAXN];

inline int min(int x, int y){
    return x < y ? x : y;
}

inline int getOneNumber(int x){
    int result = 0;
    while(x){
        result += x & 1;
        x >>= 1;
    }
    return result;
}

void init(){
    for(int i=0; i<(1<<MAXN); ++i){
        num[i] = getOneNumber(i);//这个状态有多少个1
        isEven[i] = ((num[i] & 1) == 0);//这个状态1的个数是
    }
}

int main(){
    init();
    while(~scanf("%d", &n)){
        for(int i=0; i<n; ++i)
            scanf("%d", &a[i]);
        if(n == 1){
            if(a[0])
                printf("impossible\n");
            else
                printf("0\n");
        }else{
            memset(c, 0, sizeof(c));
            for(int i=1; i<MAXM; ++i){
                for(int j=0; j<n; ++j){
                    if(a[j] & (1 << (i - 1))){
                        c[i] |= (1 << j);
                    }
                    if(c[i]){
                        m = i + 1;
                    }
                }
            }
            for(int i=0; i<=m; ++i){
                for(int j=0; j<(1<<n); ++j){
                    dp[i][j] = INF;
                }
            }
            dp[0][0] = 0;
            for(int i=1; i<=m; ++i){
                for(int j=0; j<(1<<n); ++j){
                    for(int k=0; k<(1<<n); ++k){

                        if(dp[i-1][k] != INF ){
                            if((j | (c[i] & k)) == j){
                                if(isEven[(~j) & (c[i] ^ k)] || j || num[(c[i] ^ k) | j] < n){
                                    dp[i][j] = min(dp[i][j], 
                                                   dp[i-1][k] + 
                                                   
                                                   (    !isEven[(~j) & (c[i] ^ k)] 
                                                    + num[j & (c[i] ^ k)]
                                                    + ((num[j ^ (j & (c[i] ^ k))
                                                    - num[c[i] & k]) << 1)     ) * (1 << (i-1))
                                                   ); 
                                }
                            }
                        }
                    }
                }
            }
            int ans = INF;
            for(int i=0; i<(1<<n); ++i){
                if(isEven[i]){
                    ans = min(ans, dp[m][i]);
                }
            }
            printf("%d\n", ans);
        }
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值