【GDOI2017模拟二试4.12】石子游戏

本文介绍了一个利用动态规划解决特定数值数组问题的方法,即如何将数组元素通过改变其值使得整个数组的XOR和为零,并且使改变值的总代价最小。文章详细阐述了状态压缩和状态转移的过程,最终实现复杂度的有效降低。
摘要由CSDN通过智能技术生成

题目大意

给出n个数a[i],将x改为y的代价为|x-y|,求将a变成xor和为零的最小代价。
1n15,0a[i]109

题解

考虑动态规划,按二进制从高到低,用 3n 的状态表示每个数是在k位之前是增加/不变/减少,设f[k][i][S][0/1]表示当前做到第k位,当前考虑第i位,S为每个数的状态,然后转移有:
1、若k位之前是不变,那么当前可以转成 增加/不变/减少
2、若k位之前是增加/减少,那么状态不变
时间复杂度是 O(32×n×3n)
然后是过不了的
考虑怎么把 3n 变成 2n ,就是说要把增加和减少归为同一类,对于一个数的减少,我们可以先把后面的位减成全部都为1,比如 (10100)2 可以减成 (10011)2 ,然后对于增加,就把后面的位全都变成0,这样做的好处是,当我们在后面的位里如果把增加的数的0变为1或把减少的数的1变为0都会产生贡献
对于已经确定了增加/减少的数我们称之为“不自由的”
那么可以设f[k][i][s][x][y]表示从高到低,做到第k位,当前考虑第i个数,是否自由的状态为s,当前位的xor和为x,已经确定的不自由的位在后面的位中的xor和为y
转移要分几种情况:
若第i位是自由的,那么可以考虑让它保持自由
或者将它变得不自由:
1、如果当前第k位为1,那么就要减少,算上贡献就是(a[i]&(mi[k+1]-1))-(mi[k]-1)
2、如果当前第k位为0,那么就要增加,算上贡献就是mi[k]-(a[i]&(mi[k+1]-1))
若第i位不自由,可以不更改状态直接转
或者将当前位改变,那么贡献为mi[k]

需要注意的地方是,状态中的y表示的是所有不自由位置的xor和,就是说当前位的状态不会影响这个y

Code

#include<iostream>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<algorithm>
#include<set>
#include<map>

#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)

using namespace std;

typedef long long LL;
typedef double db;

int get(){
    char ch;
    while(ch=getchar(),(ch<'0'||ch>'9')&&ch!='-');
    if (ch=='-'){
        int s=0;
        while(ch=getchar(),ch>='0'&&ch<='9')s=s*10+ch-'0';
        return -s;
    }
    int s=ch-'0';
    while(ch=getchar(),ch>='0'&&ch<='9')s=s*10+ch-'0';
    return s;
}

const int N = 16;
const int L = 33000;
const int W = 33;
const LL INF = 2e+10;
const int U = 31;

LL f[2][N][L][2][2];//s:0 for free;1 for limited;
int T,n;
int a[N][W];
LL mi[W];
LL val[N];

LL minll(LL x,LL y){
    if (x<y)return x;
    return y;
}

int main(){
    freopen("stone.in","r",stdin);
    freopen("stone.out","w",stdout);
    T=get();
    mi[0]=1;
    fo(i,1,32)mi[i]=mi[i-1]<<1;
    while(T--){
        n=get();
        fo(i,1,n){
            int x=get();
            fd(u,31,0)
            if ((mi[u]&x)>0)a[i][u]=1;else a[i][u]=0;
            val[i]=x;
        }
        fo(u,0,1)fo(i,1,n)fo(s,0,mi[n]-1)fo(x,0,1)fo(y,0,1)f[u][i][s][x][y]=INF;
        int now=0;
        f[0][n][0][0][0]=0;
        fd(k,U,0){
            int t=now^1;
            fo(i,0,n)fo(s,0,mi[n]-1)fo(x,0,1)fo(y,0,1)f[t][i][s][x][y]=INF;
            fo(s,0,mi[n]-1)fo(x,0,1)f[t][0][s][x][x]=f[now][n][s][0][x];
            fo(i,1,n)
                fo(s,0,mi[n]-1){
                    if ((mi[i-1]&s)==0){//free
                        //still free
                        fo(x,0,1)
                            fo(y,0,1)
                            f[t][i][s][x^a[i][k]][y]=minll(f[t][i][s][x^a[i][k]][y],f[t][i-1][s][x][y]);
                        //set limited
                        if (a[i][k]){//-:set 1
                            //f[t][i][s|mi[i-1]][x][y]  f[t][i-1][s][x][y]+(val[i]&(mi[k+1]-1))-(mi[k]-1)
                            fo(x,0,1)
                                fo(y,0,1)
                                f[t][i][s|mi[i-1]][x][y^1]=minll(f[t][i][s|mi[i-1]][x][y^1],f[t][i-1][s][x][y]+(val[i]&(mi[k+1]-1))-(mi[k]-1));
                        }
                        else{//+;set 0
                            fo(x,0,1)
                                fo(y,0,1)
                                f[t][i][s|mi[i-1]][x^1][y]=minll(f[t][i][s|mi[i-1]][x^1][y],f[t][i-1][s][x][y]+mi[k]-(val[i]&(mi[k+1]-1)));
                        }
                    }
                    else{
                        //stay
                        fo(x,0,1)
                            fo(y,0,1)
                            f[t][i][s][x][y]=minll(f[t][i][s][x][y],f[t][i-1][s][x][y]);
                        //trans
                        fo(x,0,1)
                            fo(y,0,1)
                            f[t][i][s][x^1][y]=minll(f[t][i][s][x^1][y],f[t][i-1][s][x][y]+mi[k]);
                    }
                }
            now=t;
        }
        LL ans=INF;
        fo(s,0,mi[n]-1)
            fo(x,0,1)
            ans=minll(ans,f[now][n][s][0][x]);
        printf("%lld\n",ans);
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值