TC SRM681 DIV2 T3 XorLists

题意

有一个序列 A A ,矩阵S表示 A A 中的元素两两的异或值。即Si,j=AixorAj,0i,jN N N 为序列的元素个数。你的任务是对于给定的S,求出满足条件的 A A 的个数,满足对于Ai,都有 0AiM 0 ≤ A i ≤ M 。答案对 109+7 10 9 + 7 取模。

约定

1N10,0M109 1 ≤ N ≤ 10 , 0 ≤ M ≤ 10 9

分析

PART 1 XOR

首先明白异或运算的一个性质:

  • axorb=c a x o r b = c ,则 b=axorc b = a x o r c

即它的运算过程是可逆的。这个很显然。
所以,也就是说,我们如果知道了这个序列的第一个数,那么剩下的数都可以用矩阵里的信息运算出来。
那么序列 A A 可行的方案数也就是序列A中第一个数 A0 A 0 可行的方案数。由于 1AiM,M109 1 ≤ A i ≤ M , M ≤ 10 9 ,所以实际上方案数是绝对不会超过 M M 的,即,模数是个废物

PART 2 计数

我们枚举第一个数的可能情况即可。
首先考虑如果没有M的限制时,方案数是无限种的。(好像说了和没说没什么区别
我们考虑有 M M 的限制时,首先我们要满足第一个数。我们可以枚举第一个数的二进制位,若当前位前的所有位都和M的对应位相同,那么当前位的选择是受 M M 的对应位限制的,否则可以随意选择01。我们可以在枚举到当前位的同时记录一个状态:第一个数的前几位是否全部与M的对应位相同,这样就比较好决定当前的决策。接下来我们需要其他 N1 N − 1 个数也满足 M M 的限制。那么我们只需要在对第一个数处理的同时,我们每确定一个当前位的选择,就通过矩阵S计算出其他数对应位是什么,并且,类似对第一个数的处理,我们也可以把其他数的这种状态记录下来。因为 N N <script type="math/tex" id="MathJax-Element-27">N</script>较小,这个用状态压缩即可。

这玩意似乎叫数位DP,你可以照着网上的板子写一个记忆化;或者循环形式也可以。这两种实现的代码后面都会贴出。

参考程序

1 我的记忆化实现

//tc is healthy, just do it
#include <bits/stdc++.h>
using namespace std;
const int MAX_N = 15;
const int MAX_B = 32;
const int MAX_S = 1100;

class XorLists {
public:
    int countLists( vector <int> s, int m );
private:
    int N, M;
    int dfs(int pos, int stat);
};

int S[MAX_N][MAX_N], DP[MAX_B][MAX_S];

int XorLists::countLists(vector <int> s, int m) {
    int i, j, k;
    N = floor(sqrt(s.size())), M = m;
//  首先检查是否有解,第一遍:检查S[i][i]是否为0,即自己异或自己是否为0
    for (i = 0; i < N; i++) {
        for (j = 0; j < N; j++) S[i][j] = s[i * N + j];
        if (S[i][i]) return 0;      // judge a[i] xor a[i]
    }
//  第二遍:检查所有A[i]xorA[j]xorA[j]xorA[k]是否等于A[i]xorA[k]
    for (i = 0; i < N; i++)
        for (j = 0; j < N; j++)
            for (k = 0; k < N; k++)     // judge whether a[i] ^ a[j] ^ a[j] ^ a[k] == a[i] ^ a[k]
                if (S[i][j] ^ S[j][k] ^ S[i][k]) return 0;
    memset(DP, 0xff, sizeof(DP));
    return dfs(31, (1 << N) - 1);   // 由于M在int内,直接从最高位开始也没有关系
}

int XorLists::dfs(int pos, int stat) {
//  pos为当前位,stat表示受限制的状态。一开始时我们可以认为所有数都是受限制的,即最高位同M都为0,所以状态为111...1,这样方便我们后面的转移
    if (pos < 0) return 1;  // pos小于0说明所有位已经枚举完(当然是一种合法方案),那么返回1
    if (DP[pos][stat] > 0) return DP[pos][stat];
    int & now = DP[pos][stat];
    int nxtnum, m_pos = M >> pos & 1, lim = stat & 1 ? m_pos : 1, i, j, nxtstat;
//  m_pos表示M的当前位,lim位当前位的限制,nxtstat表示下一个限制状态
    for (i = now = 0; i <= lim; i++) {  // i枚举当前位的选择
        nxtstat = stat & 1 & i == m_pos;    // 先加入第一个数的状态
        bool fail = false;  // fail表示该状态是否合法
        for (j = 1; j < N; j++)
            if (stat >> j & 1) {    // 如果某个数受限制,我们就要考虑一下它当前是否合法;若它已经不受限制了则可以不管它
                nxtnum = S[0][j] >> pos & 1 ^ i;    // 算出这个数的当前位
                if (nxtnum > m_pos) { fail = true; break; }
                if (nxtnum == m_pos) nxtstat |= 1 << j;     // 更新下一个限制状态
            }
        if (!fail) now += dfs(pos - 1, nxtstat);    // 累加
    }
    return now;
}

2 CHY大佬的循环实现

emmmm这个代码我不是很会解释,大家自己看吧。

#include <bits/stdc++.h>
using namespace std;

class XorLists {
public:
    int countLists( vector <int> s, int m );
};
const int MAXN = 10, MAXLOG = 32;
int n;
int dp[MAXLOG][2][1 << MAXN];
int XorLists::countLists(vector <int> s, int m) {
    n = 0;
    for(;n * n < s.size(); n++);
    for(int i = 0; i < n; i++)
        for(int j = 0; j < n; j++)
            for(int k = 0; k < n; k++)
                if((s[i * n + j] ^ s[j * n + k]) != s[i * n + k])
                    return 0;
    memset(dp, 0, sizeof(dp));
    dp[31][0][(1 << n) - 1] = 1;
    for(int i = 31; i > 0; i--)
        for(int j = 0; j <= 1; j++)
            for(int k = 0; k < 1 << n; k++) {
                if(dp[i][j][k] == 0) continue;
                for(int i1 = 0; i1 <= 1; i1++) {
                    int b = 0;
                    for(int l = 0; l < n; l++)
                        if((k >> l) & 1)
                        if((((m >> (i - 1)) & 1) == 0) && (((s[l] >> (i - 1)) & 1 ^ i1) == 1)) {
                            b = 1;
                            break;
                        }
                    if(b == 1) continue;
                    int t = k;
                    for(int l = 0; l < n; l++)
                        if((k >> l) & 1)
                        if((((m >> (i - 1)) & 1) == 1) && (((s[l] >> (i - 1)) & 1 ^ i1) == 0)) t -= 1 << l;
                    dp[i - 1][i1][t] += dp[i][j][k];
                }
            }
    int ans = 0;
    for(int i = 0; i <= 1; i++)
        for(int j = 0; j < 1 << n; j++) ans += dp[0][i][j];
    return ans;
}

总结

本题重点在于对位运算性质的掌握,如果熟练了就能很快分析出来。碰到这种情况,不妨直接拿来几个数分析分析。这题只要发现了第一个数确定后面数也确定这个性质,整个题就没什么难度了。虽然数位DP没有接触过,但自己想出来一个记忆化的形式还是不太难的。并且这题还考到了状态压缩的技巧,还是比较综合的。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值