题意
有一个序列 ,矩阵 表示 中的元素两两的异或值。即 , 为序列的元素个数。你的任务是对于给定的 ,求出满足条件的 的个数,满足对于 ,都有 。答案对 取模。
约定
分析
PART 1 XOR
首先明白异或运算的一个性质:
- 若 ,则
即它的运算过程是可逆的。这个很显然。
所以,也就是说,我们如果知道了这个序列的第一个数,那么剩下的数都可以用矩阵里的信息运算出来。
那么序列
可行的方案数也就是序列
中第一个数
可行的方案数。由于
,所以实际上方案数是绝对不会超过
的,即,模数是个废物。
PART 2 计数
我们枚举第一个数的可能情况即可。
首先考虑如果没有
的限制时,方案数是无限种的。(好像说了和没说没什么区别)
我们考虑有
的限制时,首先我们要满足第一个数。我们可以枚举第一个数的二进制位,若当前位前的所有位都和
的对应位相同,那么当前位的选择是受
的对应位限制的,否则可以随意选择01。我们可以在枚举到当前位的同时记录一个状态:第一个数的前几位是否全部与
的对应位相同,这样就比较好决定当前的决策。接下来我们需要其他
个数也满足
的限制。那么我们只需要在对第一个数处理的同时,我们每确定一个当前位的选择,就通过矩阵
计算出其他数对应位是什么,并且,类似对第一个数的处理,我们也可以把其他数的这种状态记录下来。因为
较小,这个用状态压缩即可。
这玩意似乎叫数位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没有接触过,但自己想出来一个记忆化的形式还是不太难的。并且这题还考到了状态压缩的技巧,还是比较综合的。