hihoCoder挑战赛19 A Rikka with Sequence

题目链接
tags: ACM 数论 位运算 数位dp


题目描述

众所周知,萌萌哒六花不擅长数学,所以勇太给了她一些数学问题做练习,其中有一道是这样的:

勇太有 n(1n50)  个[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];//不选取这个数

这样最后答案为

ans=i=02131dp[n][i][i] 

对于初始化有
dp[0][0][2131]=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]; //选取这个数

答案:

ans=dp[n][0][0]+dp[n][STATE(x,x)][1]  

得到可能出现的状态:

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;
}
编号题目结果语言时间内存
1279Rikka with SequenceACG++7402ms330MB
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值