Description
给出⼀种新的按位运算 ‘#’ 的真值表
定义⼀个序列的价值为序列中所有数从左向右进行’#’ 运算得到的值给定⼤小为 n 的序列列 a[],求它的 2^n-1 个⾮空子序列的价值平⽅和
n <= 50000, a[i] < 2^30
Soultion
我们考虑一个这样的S2=(ac1 # ac2 #……)2
将S表示成二进制
我们将x分解是二的幂次,若S and 2i为0,则xi=0,否则xi=2i
那么我们将S2分解后可以得到如下形式(x0+x1+...xp−1)2。
再分解上式得到x20+x0x1+...+x0xk−1+x1x0+x21+...+x1xp−1+...+。
我们发现这个式子中最多只与两个位有关。
因此我们可以递推,就是只考虑所有这样的S中第i位和第j位的累和。每次枚举i和j(
因为只有i和j都为1时相乘才为1,所以最后将2^(i+j)乘上dp[1][1]就是对答案的贡献了。
只需将这些贡献累计起来就是答案了。
总复杂度为O(p2n)。
Summary
做带有平方的题目可以考虑将平方分解。
Code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 50100
#define mod 1000000007
using namespace std;
int to[2][2],f[N][2][2],a[N];
int n,p;
void M(int &x) {if(x >= mod) x -= mod;}
int get(int p1,int p2)
{
memset(f,0,sizeof(f));
for(register int k = 1;k <= n;k++) {
int n1 = (a[k]>>p1)&1,n2 = (a[k]>>p2)&1;
M(++f[k][n1][n2]);
for(int i = 0;i < 2;i++)
for(int j = 0;j < 2;j++) {
M( f[k][to[i][n1]][to[j][n2]] += f[k-1][i][j] );
M( f[k][i][j] += f[k-1][i][j] );
}
}
return f[n][1][1];
}
int main()
{
scanf("%d%d",&n,&p);
for(int i = 0;i < 2;i++) for(int j = 0;j < 2;j++) scanf("%d",&to[i][j]);
for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
int ans = 0;
for(int i = 0;i < p;i++)
for(int j = 0;j < p;j++)
ans = (ans + (1ll<<(i+j)) % mod * get(i,j)) % mod;
printf("%d\n",ans);
return 0;
}