Math teacher's homework UVA - 1489 (脑跑数位dp +组合计数)

网上对这题的分析寥寥无几,分析的更是惨不忍睹,让人望而生畏。

我参考的博客  他写的思路是有道理的,但不看几个小时是无法理解的。

我的想法:首先这题,你不可能像别的数位dp一样,真的去dfs写出了,因为它是个计算方案数的问题,并且可选的数有n个。

但它还是一个数位dp,为什么?因为它还是利用了枚举位的思想去解决。

首先第一个思想:它最后要求要异或值为k,我们可以转换一下思路,我们不盯着最后结果为k来思考问题,我们盯着一个全局的:它能异或组成任意一个数(竟然能组成任意一个数了,自然也就能组成k啦)。

第二个思想:怎么样定义状态方程? 可以这样考虑:dp[i][j][k](其中k只取0或1),表示,前i个数,对应的二进制位为:0到j-1位,能组成小于等于2^(j-1)里的任意数,且第j位为k(K==0或k==1)的方案数。

可能读起来比较拗口,给个例子解释一下,比如只有1个数 a=3,a=11(3的二进制):那么dp[1][1][0]=1,因为2^1位为1,所以,小于2的数都可以去,也就是说,2^(1-1)一下的任意数都可以组成,这样的方案只有1个,因为只有一个数a。

有了这个方程,其实我们可以推出如何求最后答案,ans+=dp[n][i][k的第i位的值为多少] ,什么意思?也就是说,前i-1位已经可以组成小于等于2^(i-1)任意数了,并且只有第i位和k的第i位也一样,那么这样的方案是可以组成k的。

 

第三个思想:状态转移方程:我们考虑由前i项的方案数来推i+1项的方案数,考虑第i+1项的每一位,只有含有1,说明在这一位之前的任意状态都是可达的(因为比它小的数都可以取)。那么我们再此时由dp[i][j][0/1]转移到dp[i+1][j][0/1]时,第i+1个数的前

k项,k<=j都是任意的。(因为已经满足可以组成任意一个数了)。

考虑dp[i][j][0/1]转移到dp[i+1][k][0/1],k>j,这个时候因为前j个已经满足任意取,第i+1项,只要提供j+1到k 的任意取就行,所以,前j项还是可以随便取。

 

第四个思想:有了前面的三个思想,大致的解题方法应该已经出来了。(其实你还是不能实现),因为细节还是太多。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int maxn=60;
const int mod=1000000003;
ll dp[maxn][maxn][2];
ll a[maxn];
int xo[maxn][maxn];
int n;
ll k;
int main()
{
    #ifndef ONLINE_JUDGE
        freopen("in.txt","r",stdin);
        freopen("out.txt","w",stdout);
    #endif
    ll one=1;
    while(scanf("%d%lld",&n,&k)!=EOF&&(n+k))
    {
        for(int i=1;i<=n;i++)
        {
            scanf("%lld",&a[i]);
            a[i]++;
        }
        memset(dp,0,sizeof(dp));
        for(int i=0;i<=31;i++)
        {
            if(a[1]&(one<<i))
            {
                dp[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(dp[i][j][0]||dp[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]&(one<<j))==0)//i+1位不会贡献1
                                {
                                    dp[i+1][j][0]+=dp[i][j][0]*(one<<k)%mod;
                                    dp[i+1][j][1]+=dp[i][j][1]*(one<<k)%mod;
                                }
                                else
                                {
                                    dp[i+1][j][0]+=dp[i][j][1]*(one<<k)%mod;
                                    dp[i+1][j][1]+=dp[i][j][0]*(one<<k)%mod;
                                }
                                dp[i+1][j][0]%=mod;
                                dp[i+1][j][1]%=mod;
                            }
                            else
                            {
                                dp[i+1][k][xo[k][i]]+=(dp[i][j][0]+dp[i][j][1])%mod*(one<<j)%mod;
                                dp[i+1][k][xo[k][i]]%=mod;
                            }
                        }
                    }
                }
            }
        }
        ll ans=0;
        for(int i=31;i>=0;i--)
        {
            int tmp=(k&(one<<i))==0?0:1,has=0;
            ans+=dp[n][i][tmp];
            ans%=mod;
            for(int j=1;j<=n;j++)
            {
                has^=(a[j]&(one<<i))==0?0:1;
            }
            if(has!=tmp) break;
            //printf("%d...\n",i);
        }
        printf("%lld\n",ans);
    }
    return 0;
}

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值