题意:
现在有
2
n
2^{n}
2n种状态,从0开始,定义三元组
{
a
,
b
,
c
}
\{a,b,c\}
{a,b,c}包括
d
d
d当且仅当
d
d
d是
a
∣
b
∣
c
a|b|c
a∣b∣c的子集。
a属于f()函数,b属于g()函数,c属于r()函数。
现在给你所有的f(x),g(x),r(x)的返回值
0
<
=
x
<
2
n
0<=x<2^n
0<=x<2n,定义r(d)是所有
f
(
a
)
∗
g
(
b
)
∗
r
(
c
)
f(a)*g(b)*r(c)
f(a)∗g(b)∗r(c)的和当且仅当d是a,b,c三元组的子集。
问你
∑
i
=
0
2
n
−
1
r
(
i
)
\sum^{2^n-1}_{i=0}r(i)
∑i=02n−1r(i)的值
题解:
有点难度的SOSDP了,这道题不能直接将答案存在DP数组里,而是要知道对于 a ∣ b ∣ c = x a|b|c=x a∣b∣c=x这种状态,总共有 2 d ( x ) 2^{d(x)} 2d(x)个r会受到它影响 d ( x ) d(x) d(x)表示x状态下二进制位上1的个数(这个理解一下就懂了)。那么我们用3个dp求出f,g,r三种函数的SOSDP,再将其相乘变成对应的乘积,对于乘积我们再做一次高维前缀和,这样 m u l [ x ] mul[x] mul[x]就表示 a ∣ b ∣ c a|b|c a∣b∣c是x的所有子集的答案的和,所以我们要剪掉所有 m u l [ x ] mul[x] mul[x]的子状态的和,由与它是用高维前缀和推过来的,所以容斥只需要一次即可,这样会将所有多余情况剪掉。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1<<20;
const ll mod=1e9+7;
ll f[N],g[N],r[N],mul[N],p[21];
int main()
{
p[0]=1;
for(int i=1;i<=20;i++)
p[i]=p[i-1]*2;
int n;
scanf("%d",&n);
for(int i=0;i<1<<n;i++)
scanf("%lld",&f[i]);
for(int i=0;i<1<<n;i++)
scanf("%lld",&g[i]);
for(int i=0;i<1<<n;i++)
scanf("%lld",&r[i]);
ll ans=0;
for(int i=0;i<20;i++)
for(int j=0;j<N;j++)
if(j&(1<<i))
{
f[j]=(f[j]+f[j-(1<<i)])%mod;
g[j]=(g[j]+g[j-(1<<i)])%mod;
r[j]=(r[j]+r[j-(1<<i)])%mod;
}
for(int i=0;i<N;i++)
mul[i]=f[i]*g[i]%mod*r[i]%mod;
for(int i=0;i<20;i++)
for(int j=0;j<N;j++)
if(j&(1<<i))
mul[j]=(mul[j]-mul[j-(1<<i)]+mod)%mod;
for(int i=0;i<N;i++)
{
int sum=0;
for(int j=0;j<20;j++)
if(i&(1<<j))
sum++;
ans=(ans+mul[i]*p[sum])%mod;
}
printf("%lld\n",ans);
return 0;
}