题目链接: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;
}