习题:神奇的NIM游戏(数位DP&状压)

醉酒当歌 提交于 2019-12-14 04:20:18

题目

思路

神仙数位DP题目

最开始想到的是三进制状压

每一位表示这个数不变,增加还是减少

\(dp_{k,s,i,0/1}\)表示考虑到第k位,状态为s,正在考虑第i个数,第k的异或和为0/1

这样一来,

时间复杂度就为\(O(32*n*3^n)\)

很明显瞬间爆炸

但是我们仔细思考增加和减少

如果这个数的比他高的位数已经将后面的值整体赋值

如果是增加

那么代价就是\(2^i\)

如果是减少呢?

代价也是\(2_i\)

也就是说我们的状态可以转换成二进制

但是我们需要多增加一位来表示这一位是否被比他高的位数整体赋值

笔者称其为约束

\(dp_{k,s,i,0/1,0/1}\)表示考虑到第k位,状态位s,第i个数,第s位的异或和为0/1,第i个数的第k位是否被约束

首先我们考虑DP的转移,可以发现,当前第k位的状态只与第k+1位的状态有关

也就是k我们可以滚动

第i个数也是完全没有必要的

直接修改就行了

笔者太菜直接贴了STD的代码

代码

#include <cstdio>
#include <cstring>
#include <algorithm>

#define rep(i, x, y) for (int i = (x), _ = (y); i <= _; ++i)
#define down(i, x, y) for (int i = (x), _ = (y); i >= _; --i)
#define x first
#define y second
#define mp make_pair
#define pb push_back
#define bin(x) (1<<(x))
//#define LX_JUDGE

using namespace std;
typedef long long LL;
typedef pair<int, int> pii;

template<typename T> inline void upmax(T &x, T y) { x < y ? x = y : 0; }
template<typename T> inline void upmin(T &x, T y) { x > y ? x = y : 0; }

template<typename T>
inline void read(T &x) {
    char c;
    while ((c = getchar()) < '0' || c > '9');
    for (x = c - '0'; (c = getchar()) >= '0' && c <= '9'; x = x * 10 + c - '0');
}

const int inf = 0x3f3f3f3f;
const int maxn = 15;

int dp[2][bin(maxn) + 1][2][2];
int a[maxn], n;

void Solve() {
    read(n);
    rep (i, 0, n - 1) {
        read(a[i]);
    }
    memset(dp[0], 0x3f, sizeof(dp[0]));
    dp[0][0][0][0] = 0;
    /*
    原状态为dp[s][i][0/1][0/1]表示考虑到第s高位,每一个数是否能增减的状态为i,第k位的异或位0/1 第k位是否被高位所束缚 
    但是我们考虑到因为我们是从高到低枚举,
    所以s这一位可以滚动
    */
    int fr = 0, to = 1;
    down (s, 30, 0) //每一位
    {
        rep (i, 0, n - 1) //枚举每一个数
        {
            int v = (a[i] >> s) & 1;//第a[i]个数的s为是否为1
            int cost = a[i] & (bin(s) - 1);//除去最高位到第s位之后的a[i]
            cost = !v ? bin(s) - cost : cost + 1;//如果当前的位置是0,把他变为全0,否则就变为全1,所造成的花费,并且是第一次改的
            memset(dp[to], 0x3f, sizeof(dp[to]));
            rep (j, 0, bin(n) - 1) //枚举每一个数能否增减
            {
                rep (l, 0, 1) //枚举第s位的异或值
                    rep (o, 0, 1) //枚举当前这一位是否被约束
                    {
                        int x = dp[fr][j][l][o];
                        if (j & bin(i)) //已经被修改过
                        {
                            upmin(dp[to][j][l][o], x);//从上一个数字原地转移
                            upmin(dp[to][j][l ^ 1][o], x + bin(s));//l^1表示异或变化,所导致的费用增加,因为不是第一次改,第s位的增减的费用变化是2^s,
                        } 
                        else //没有被修改过
                        {
                            upmin(dp[to][j][l ^ v][o], x);//一样的原地转移
                            upmin(dp[to][j | bin(i)][l ^ v ^ 1][o ^ v], x + cost);
                            //j|bin(i)表示第i个数发生了变化,l^v^1表示当前的异或变化,o^v表示当前第i位强行打破了约束,或者成为了约束
                        }
                }
            }
            swap(fr, to);//滚动
        }
        rep (j, 0, bin(n) - 1) //初始化
        {
            dp[fr][j][1][0] = inf;
            dp[fr][j][1][1] = dp[fr][j][0][1];
            dp[fr][j][0][1] = inf;
        }
    }
    int ans = inf;
    rep (i, 0, bin(n) - 1) {
        upmin(ans, min(dp[fr][i][0][0], dp[fr][i][1][1]));
    }
    printf("%d\n", ans);
}

int main() {
#ifdef LX_JUDGE
    freopen("in.txt", "r", stdin);
#endif
    freopen("a.in", "r", stdin);
    freopen("a.out", "w", stdout);
    int kase;
    read(kase);
    while (kase--) {
        Solve();
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!