HDU 4901 The Romantic Hero

本文提供了一道关于数列处理的算法题的解决方案,通过使用动态规划来统计特定条件下数列中两个子集的组合数量。重点介绍了如何避免重复计算以及最终答案的计算方式。

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4901


题意:在n个数的序列中,选出两个集合S,T,使得T集合位置最小的数在S集合位置最大的数的前面(两个集合不为空,不能重叠)。求有多少种选定S和T的方法,使得S集合的异或和等于T集合的与之和。


思路:我们可以用f[i][j]表示前i个数使得异或和为j的方案数,g[i][j]表示后i个数与之和为j的方案数。

f[i][j] = f[i-1][j] , f[i][ a[i] ]++ ,  f[i][j^a[i]] += f[i-1][j]  ,分别是先不加第i个数的方案,第i个数单独算一个方案,第i个数和前i-1个数配合的方案。g数组同理。

预想是处理出来数组就可以直接枚举取值和位置累加答案,但是样例没过,原因是f[i][j]会和f[k][j](k<i)中的一些方案算重,也就是说没有用到k+1~i这些位置的数,所以我们再处理出来一个h[i][j]数组表示前i个数,且一定包含i在内的异或和为j的方案数。这样∑h[i][j]*g[n-i][j]就是答案,注意计算中的取模。


#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <stack>
#include <map>
#include <set>
#include <vector>
#include <sstream>
#include <queue>
#include <utility>
using namespace std;

#define rep(i,j,k) for (int i=j;i<=k;i++)
#define Rrep(i,j,k) for (int i=j;i>=k;i--)

#define Clean(x,y) memset(x,y,sizeof(x))
#define LL long long
#define ULL unsigned long long
#define inf 0x7fffffff
#define mod 1000000007
const int maxn = 1024;
LL f[maxn+10][maxn+10];
LL g[maxn+10][maxn+10];
LL h[maxn+10][maxn+10];
int a[maxn+10];
int n;
void init()
{
    scanf("%d",&n);
    Clean(f,0);
    Clean(g,0);
    rep(i,1,n)
    {
        scanf("%d",&a[i]);
        rep(j,0,maxn) f[i][j] = f[i-1][j];
        f[i][a[i]]++;
        rep(j,0,maxn) f[i][j^a[i]] += f[i-1][j];
        rep(j,0,maxn) f[i][j] %= mod;
    }
    Rrep(i,n,1)
    {
        int pos = n - i + 1;
        rep(j,0,maxn) g[pos][j] = g[pos-1][j];
        g[pos][a[i]]++;
        rep(j,0,maxn) g[pos][j & a[i]] += g[pos-1][j];
        rep(j,0,maxn) g[pos][j] %= mod;
    }
    Clean(h,0);
    rep(i,1,n-1)
    {
        h[i][a[i]]++;
        rep(j,0,maxn)
            h[i][ a[i] ^ j ] += f[i-1][j];
    }
}

LL solve()
{
    LL ans = 0;
    rep(i,0,maxn)
        rep(j,1,n-1)
        {
            ans = ( ans + (h[j][i]*g[n-j][i]) ) % mod;
        }
    return ans;
}

int main()
{
    int T;
    cin>>T;
    while(T--)
    {
        init();
        printf("%I64d\n",solve());
    }
    return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值