hdu 3693 Math teacher's homework(数位dp)

题意:给出n个数,每个数字代表0到这个数字的范围,每个里面要选一个,求异或和为K的情况总数。

做法:挺难想的一个dp,我们把这些数换成2进制组成一个表格,首先可以明确一点,只要有一位可以随便放的二进制数的位(因为前面有的位已经使得比原数小了)不随便放,那么其他数这个位的0或1随便放,都可以达到想要的答案。而每一个位只需要一个这样的位就可以达到效果了,所以我们就规定第一个能随便放的地方不随便放,这样后来这个位就真的可以随便放了。

所以可以用dp[i][j]代表到前i个数,前0-j-1的位数的组合可以得到任意一个数的情况总数。那么枚举下一个数二进制1的位置(把这个1变成0后面的就随便放了)的时候就要把位置跟j比较,如果比j小,那么从这个位置到j的位置是这几个位数第一个出现随便放的位置,那么我们就不让他随便放,在j到0之间的则可以随便放(因为前面已经有不随便放的位置故可以得到任何想得到的答案)。反之,就那个位置到0可以随便放。

最后算答案的时候,0到j-1是可以得到想得到的数的,j+1到最大位是跟原数是一样的。一个一个异或跟K比较下,如果不等说明不满足。但是关键是j那个位置不知道到底是1还是0,而且这一位也不是想得到什么就能得到什么的。所以我们把dp再加上一维,dp[i][j][k]代表前i个数,前0到j-1的位数组合可以得到任意一个数,而且第j位组合可以得到k(0或者1)的情况总数。这样我们根据K的那一位为几就把答案加上谁就可以了。

#pragma comment(linker, "/STACK:102400000,102400000")
#include<cstdio>
#include<ctype.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<vector>
#include<cstdlib>
#include<stack>
#include<queue>
#include<set>
#include<map>
#include<cmath>
#include<ctime>
#include<string.h>
#include<string>
#include<sstream>
#include<bitset>
using namespace std;
#define ll __int64
#define ull unsigned __int64
#define eps 1e-8
#define NMAX 1000000000
#define MOD 1000000003
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define PI acos(-1)
template<class T>
inline void scan_d(T &ret)
{
    char c;
    int flag = 0;
    ret=0;
    while(((c=getchar())<'0'||c>'9')&&c!='-');
    if(c == '-')
    {
        flag = 1;
        c = getchar();
    }
    while(c>='0'&&c<='9') ret=ret*10+(c-'0'),c=getchar();
    if(flag) ret = -ret;
}
const int maxn = 55;
ll g[55][55][2],a[55];
int xo[55][55];
int main()
{
#ifdef GLQ
    freopen("input.txt","r",stdin);
//    freopen("o1.txt","w",stdout);
#endif
    int n;
    ll K;
    ll one = 1;
    while(~scanf("%d%I64d",&n,&K) && n+K)
    {
        for(int i = 1; i <= n; i++)
            scanf("%I64d",&a[i]),a[i]++;
        memset(g,0,sizeof(g));
        for(int i = 0; i <= 31; i++) if(a[1]&(one<<i))
            g[1][i][0] = 1;
        for(int i = 0; i <= 31; i++)
            for(int j = 1; j <= n; j++)
                if(j == 1) xo[i][j] = (a[j]&(one<<i)) == 0 ? 0 : 1;
                else xo[i][j] = xo[i][j-1]^((a[j]&(one<<i)) == 0 ? 0 : 1);
        for(int i = 1; i < n; i++)
            for(int j = 0; j <= 31; j++) if(g[i][j][0] || g[i][j][1])
                for(int k = 0; k <= 31; k++) if(a[i+1]&(one<<k))
                {
                    if(k <= j)
                    {
                        if(k == j || (a[i+1]&(1<<j)) == 0)
                        {
                            g[i+1][j][0] += g[i][j][0]*(one<<k)%MOD;
                            g[i+1][j][1] += g[i][j][1]*(one<<k)%MOD;
                        }
                        else
                        {
                            g[i+1][j][0] += g[i][j][1]*(one<<k)%MOD;
                            g[i+1][j][1] += g[i][j][0]*(one<<k)%MOD;
                        }
                        g[i+1][j][0] %= MOD;
                        g[i+1][j][1] %= MOD;
                    }
                    else
                    {
                        g[i+1][k][xo[k][i]] += (g[i][j][0]+g[i][j][1])%MOD*(one<<j)%MOD;
                        g[i+1][k][xo[k][i]] %= MOD;
                    }
                }
        ll ans = 0;
//        cout<<g[n][0][0]<<endl;
        for(int i = 31; i >= 0; i--)
        {
            int tmp = (K&(one<<i)) == 0 ? 0 : 1,ha = 0;
//            cout<<i<<" "<<tmp<<" "<<g[n][i][tmp]<<endl;
            ans += g[n][i][tmp];
            ans %= MOD;
            for(int j = 1; j <= n; j++)
                ha ^= ((a[j]&(one<<i)) == 0 ? 0 : 1);
            if(ha != tmp) break;
        }
        printf("%I64d\n",ans);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值