题目链接
tags: ACM 数论 位运算 数位dp
题目描述
众所周知,萌萌哒六花不擅长数学,所以勇太给了她一些数学问题做练习,其中有一道是这样的:
勇太有 n(1≤n≤50) 个[0,8192)中的整数,现在六花可以从中选出若干个数(不可以不取),她的方案需要满足她选出的所有数的异或和恰好等于它们AND(二进制与运算)起来的值,现在勇太想让六花求出满足条件的方案数。
当然,这个问题对于萌萌哒六花来说实在是太难了,你可以帮帮她吗?
解法
不能过的dp
定义 dp[i][j][k] 为前i个数中选取若干个数,使它们的异或和为 j , 按位与之和为 k , 即如果选取的数为 b1,b2,⋯,bm] , 则
j = b[1] ^ b[2] ^ ... ^ b[m]
k = b[1] & b[2] & ... & b[m]
那么就有转移
dp[i][ j^a[i] ][ k&a[i] ] += dp[i-1][j][k];//选取这个数
dp[i][j][k] += dp[i - 1][j][k];//不选取这个数
这样最后答案为
对于初始化有
dp[0][0][213−1]=1 , 其他的都为0
时间复杂度为: O(n×213×213) 并不能过这道题.
能过的dp
考虑若干个数的异或和x, 位与和y间的关系. 如果数的个数为奇数, 则x&y=y, 如果个数为偶数, 则x&y=0, 所以可以用 2×313 种状态表示所有的状态, 即三元组(xx, y, odd), 分别表示不计都为1的位的异或和xx (xx = x & (~y)), 位与和y, 和选择的数的个数的奇偶(奇odd=1, 偶odd=0). 这样每一位都有三个状态:
- 都为1 (xx 该位为0, y该位为1),
- 有奇数个1 (xx该位为1, y该位为0),
- 有偶数个1 (xx该位为0, y该位为0)
再乘以odd的两种状态(0, 1), 这样一共有
2×313
种状态.
三元组(xx, y, odd)与异或和x, 位与和y相互转换
//(xx, y, odd) to (x, y, odd)
if(odd == 1) x = xx;
else x = xx | y;
//(x, y, odd) to (xx, y, odd)
xx = x & (~y)
有了这个转换, 就能拿将上面的dp改写得到可以过的dp
定义
dp[i][STATE(x,y)][k]
为前i个数, 异或和为x, 位与和为y对应的状态
STATE(x,y)
, 选取个数为奇数(k=1)或偶数(k=0)的方案数.
初始化,
dp[0][STATE(0,8191)][0]=1
, 其他为0
转移:
dp[i][STATE(x, y)][k] += dp[i][STATE(x, y)][k]; //不选取这个数
dp[i][STATE(x^a[i], y&a[i])][k] += dp[i][STATE(x, y)][k]; //选取这个数
答案:
得到可能出现的状态:
for(int x = 0; x < 8192; ++x)
for(int y = 0; y < 8192; ++y)
if((x&y) == y || (x&y) == 0)
{
//ADD STATE (x, y)
}
也可以通过枚举((~y)&(8192-1))的子集的方式得到可能出现的状态.
for(int y = 0; y < 8192; ++y)
{
int yy = ((~y)&(8191));
for(int x = yy; ; x = (x - 1) & yy)
{
//ADD STATE(x, y)
if(!x) break;
}
}
时间复杂度: O(n×2×313)
官方题解
直接暴力DP的状态数是 O(n×413) 的,这样会超时。
我们可以对每一个二进制位记录一个值,当这个值为2的时候,表示所有选出来的数中,这一位都是1;对于其它情况,如果选出来的数有奇数个1,那么值为1,否则就为0。
不难发现按照上述状态进行DP,只要再记录一下选出来的数的个数的奇偶性,就能从状态中直接得到选出来的所有数的异或值和and和,这样状态数就缩减到了 O(n×313) ,就能过了。
代码
#include <stdio.h>
#include <algorithm>
typedef long long LL;
const int NN = 1594323;
const int m = 13;
int n, a[55];
int id[8192][8192], N;
int Xor[NN], And[NN];
LL dp[2][NN][2], ans;
void pre()
{
N = 0;
for(int i = 0; i < 8192; ++i)
{
int j = ((~i) & 8191);
for(int x = j; ; x = (x - 1) & j)
{
And[N] = i; Xor[N] = x;
id[i][x] = N++;
if(!x) break;
}
}
}
int main()
{
#ifdef ACM_TEST
freopen("in.txt", "r", stdin);
#endif // ACM_TEST
scanf("%d", &n);
for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
pre();
int now = 0, nxt = 1;
dp[now][id[8191][0]][0] = 1;
int x, y;
for(int i = 1; i <= n; ++i)
{
for(int j = 0; j < N; ++j)
{
dp[nxt][j][0] = dp[now][j][0];
dp[nxt][j][1] = dp[now][j][1];
}
for(int j = 0; j < N; ++j)
{
for(int k = 0; k < 2; ++k)
{
if(dp[now][j][k] == 0) continue;
x = Xor[j]; y = And[j];
if(k) x |= y;
x ^= a[i];
y &= a[i];
x &= ~y;
dp[nxt][id[y][x]][!k] += dp[now][j][k];
}
}
std::swap(now, nxt);
}
ans = dp[now][id[0][0]][0];
for(int i = 1; i < N; ++i)
{
if(Xor[i] == 0) ans += dp[now][i][1];
}
printf("%lld\n", ans);
return 0;
}
编号 | 题目 | 结果 | 语言 | 时间 | 内存 |
---|---|---|---|---|---|
1279 | Rikka with Sequence | AC | G++ | 7402ms | 330MB |