首页 > 代码库 > hdu 4901 The Romantic Hero (dp+背包问题)

hdu 4901 The Romantic Hero (dp+背包问题)

题意:

有n个数,从n个数中选出两个集合s和集合t,保证原序列中,集合s中的元素都在

集合t中元素的左边。且要求集合s中元素做抑或运算的值与集合t中元素做与运算的

值相等。问能选出多少种这样的集合s和t。


算法:

左右dp。

用dp[i][j]表示前i个数 做抑或运算得到j的方法数。最后一个值取不取到都不一定。

故为背包的问题。右边也是一样。

枚举时可能出现重复。枚举到第i个和枚举第i+1个可能重复。所以要枚举一个中间值。

这个中间值是归到s集的,因为抑或支持逆运算,而与是不支持的。

所以最后dp方程应该改为  前i个数一定包含第i个做抑或得到值j的方法数,即s[i][j]。

那么s[i][j] = dp[i-1][j^a[i]]。


右边也是做与运算,但要注意下标。


然后取long long应该在乘法之前,否则乘法运算后可能已经溢出,再做long long 就没意义了。


#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;

const int mod = 1000000000+7;
typedef long long ll;
int a[1010];
int dp[1010][1025],dp1[1010][1025],s[1010][1025];

int main()
{
    int T,n;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        memset(dp,0,sizeof(dp));
        dp[0][0] = 1;
        for(int i=1;i<=n;i++)
        {
            for(int j=0;j<1024;j++)
            {
                dp[i][j] = dp[i-1][j]+dp[i-1][j^a[i]];
                if(dp[i][j]>=mod)
                    dp[i][j] -= mod;
            }
            for(int j=0;j<1024;j++)
                s[i][j] = dp[i-1][j^a[i]];
        }
        memset(dp1,0,sizeof(dp1));
        for(int i=n;i>=1;i--)
        {
            dp1[i][a[i]]++;
            for(int j=0;j<1024;j++)
            {
                dp1[i][j&a[i]] = (dp1[i][j&a[i]]+dp1[i+1][j])%mod;
                dp1[i][j] = (dp1[i][j]+dp1[i+1][j])%mod;
            }
        }
        int cnt = 0;
        for(int k=1;k<=n-1;k++)
        {
            for(int j=0;j<1024;j++)
            {
                cnt = (cnt+(ll)s[k][j]*dp1[k+1][j])%mod;
                if(cnt>=mod) cnt-=mod;
            }
        }
        printf("%d\n",cnt);
    }
    return 0;
}