六、转化求和顺序(線形和への分解)

例题1

题意

有一个长为 \(n\) 的数组 \(a\)。求在 \(a\) 中选择 \(k\) 个数的所有方案中,每次选择的所有数的中位数的和。\(n\le 10^5,k\) 为偶数。

解法

设 \(B=\frac k2-1\)。排序后,设在最中间的两个数为 \(a_i,a_j\),则 \(\frac 12(a_i+a_j)\) 在所有中位数里出现了 \(\binom{i-1}B\binom{n-j-1}B\) 次,则答案为 \(\frac 12\sum_{i=1}^n\sum_{j=i+1}^n(a_i+a_j)\binom{i-1}B\binom{n-j-1}B\),化简可以变成 \(\frac 12\sum_{i=1}^n\binom{i-1}B\left(a_i\left(\sum_{j=i+1}^n\binom{n-j-1}B\right)+\sum_{j=i+1}^n\binom{n-j-1}Ba_j\right)\),后缀和优化计算即可。

例题2

求 \(\sum_{x=0}^n\sum_{y=0}^m x{\,{\rm{XOR}}\,}y\)。\(n,m\le 10^9\)。

解法

考虑将每个 \(\rm{XOR}\) 的每一位拆开,然后合并每一位。设 \(B_{x,j,0/1}\) 为满足 \(\le x\) 且第 \(j\) 位为 \(0/1\) 的数的数量(显然可以 \(O(1)\) 计算),则第 \(j\) 位造成的贡献为 \(2^j(B_{n,j,0}B_{m,j,1}+B_{n,j,1}B_{m,j,0})\)。

例题3

给定一棵大小为 \(n\) 的无根树,定义 \(f(i)\),对于所有大小为 \(i\) 的点集,求出能够包含它的最小连通块大小之和。对于 \(i=1 \sim n\) 的所有 \(i\),求出 \(f(i)\bmod 924844033\)。\(n\le 2\times 10^5\)。(\(924844033=2^{21}\times 441+1\),原根为 \(5\))

解法

考虑将“能够包含每个大小为 \(i\) 的点集的最小连通块大小之和”在每个点而非每个连通块处计算贡献,则其可以转化为“对于每个点,求作为包含某个大小为 \(i\) 的点集的最小连通块中包含了该点的连通块数量”。

设以某个点 \(u\) 为根时,其的每棵子树内大小分别为 \(siz_{u,1},siz_{u,2},\cdots,siz_{u,s_u}\),则某个最小连通块内不包含 \(u\) 的充要条件是内部每个点都在 \(u\) 儿子的子树内,\(u\) 对 \(f(i)\) 的贡献即为 \(\binom ni-\sum_{j=1}^{s_u}\binom{siz_{u,j}}i\),\(f(i)\) 即为 \(n\binom ni-\sum_{u=1}^n\sum_{j=1}^{s_u}\binom{siz_{u,j}}i\)。设 \(c_i=\sum_{u=1}^n\sum_{j=1}^{s_u}[siz_{u,j}=i]\),则 \(f(i)=n\binom ni-\sum_{j=1}^nc_j\binom ji\)。将 \(\binom ji\) 拆成 \(\frac{j!}{i!(j-i)!}\) 的形式,令 \(F_j=(n-j)!\),则 \(\sum_{j=1}^nc_j\binom ji=\sum_{j=1}^n(c_jj!)F_{n+i-j}\),就是标准的卷积形式了(差卷积)。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=200010;
const int maxd=maxn<<1;
const int maxp=524300;
const int md=924844033,G=5;
int n,i,j,k,u,v,t,p,le,lm;
int h[maxn],c[maxp],d[maxp];
int r[maxp],fac[maxn],inv[maxn];
struct edge{int to,nxt;}E[maxd];
inline int Pow(int d,int z){
int r=1;
do{
if(z&1) r=(1LL*r*d)%md;
d=(1LL*d*d)%md;
}while(z>>=1);
return r;
}
int dfs(int p,int f){
int lp,to,sz=1,nw;
for(lp=h[p];lp;lp=E[lp].nxt){
to=E[lp].to; if(to==f) continue;
nw=dfs(to,p); sz+=nw; ++c[nw];
}
++c[n-sz]; return sz;
}
inline void Add(int &x,int y){x-=((x+=y)>=md)*md;}
void DFT(int *f){
for(i=1;i<t;++i) if(i<r[i]) swap(f[i],f[r[i]]);
for(i=1,le=2;le<=t;i=le,le<<=1){
v=Pow(G,(md-1)/le);
for(j=0;j<t;j+=le){
for(k=0,u=1;k<i;++k,u=(1LL*u*v)%md){
p=(1LL*f[i+j+k]*u)%md;
Add(f[i+j+k]=f[j+k],md-p);
Add(f[j+k],p);
}
}
}
}
inline int C(int x,int y){return ((1LL*fac[y]*inv[x])%md*inv[y-x])%md;}
int main(){
fac[0]=1;
for(i=1;i<maxn;++i) fac[i]=(1LL*fac[i-1]*i)%md;
inv[maxn-1]=Pow(fac[maxn-1],md-2);
for(i=maxn-1;i;--i) inv[i-1]=(1LL*inv[i]*i)%md;
scanf("%d",&n);
for(i=1;i<n;++i){
scanf("%d%d",&u,&v);
E[++t]={u,h[v]}; h[v]=t;
E[++t]={v,h[u]}; h[u]=t;
}
dfs(1,0); c[0]=0; d[0]=fac[n];
for(i=1;i<=n;++i){
d[i]=inv[n-i];
c[i]=(1LL*c[i]*fac[i])%md;
}
for(u=n<<1,t=1;t<u;t<<=1);
for(le=t>>1,i=0;i<t;++i)
r[i]=(r[i>>1]>>1)+(i&1)*le;
DFT(c); DFT(d);
for(i=0;i<t;++i) c[i]=(1LL*c[i]*d[i])%md;
DFT(c); reverse(c+1,c+t); u=Pow(t,md-2);
for(i=1,j=n+1;i<=n;++i,++j){
v=((1LL*c[j]*u)%md*inv[i])%md;
Add(v=md-v,(1LL*n*C(i,n))%md);
printf("%d\n",v);
}
return 0;
}

七、置换群相关知识(部分群のテクニック)

置换群相关知识可以用于解决某些不可重计数问题。然而自认为后面的两个题的解法和置换群相关知识联系不大,需要者可以自行看 OI Wiki。不过这里的题目都是和“如何交换两个可交换的位置以尽量少影响其他位置/找出能够表示其他所有操作的单位操作”的思路有关。

例题1:Reverse Grid

题意

有一个 \(H\times W\) 的矩阵 \(a\),可以进行若干次操作,每次选择一行或一列翻转,求最后能够生成的本质不同的矩阵有多少种。\(H,W\le 200,\) 矩阵内的元素均为小写字母。

解法

考虑某个 \(a_{i,j}\) 只会变到 \(a_{H-i+1,j},a_{i,W-j+1},a_{H-i+1,W-j+1}\) 的位置。(对于 \(H,W\) 为奇数的情况,可以直接乘上中间能否变化的方案数,则其他元素都有四种可能出现的位置)所以这四个元素可以看成一个四元组整体处理。设四个位置的数分别为 \(a,b,c,d\),则可以通过下面的方式使得只有 \(a,b,c,d\) 之间的顺序进行变化(箭头表示行/列内其他元素的顺序):

同理其他任意三个元素也可以按照这样的方式变化,打表可以发现这些元素出现的顺序有 \(12\) 种。同时可以发现将同在一行/一列的元素调换位置可得所有 \(4!\) 种方式中另外 \(12\) 种出现方式,意味着某行被翻转将导致若干个四元组能够变换的位置同时变化。注意如果某个四元组内部出现了相同的数,则它们一定可以在某次变化后出现在同一行,它们在被翻转某行/某列后能够变换出的顺序一样,可以把对应的贡献先乘上;否则在翻转某行/某列后对应能够变换出的顺序会变化。

考虑将每一行和每一列看成点,然后对于某个四元组 \(\{a_{i,j},a_{H-i+1,j},a_{i,W-j+1},a_{H-i+1,W-j+1}\}(2i\le H,2j\le W)\),在第 \(i\) 行和第 \(j\) 列之间连边。在翻转某行/某列之后对应出边的四元组状态也会改变,所以可以对于某个连通块 \(S\) 求出某棵生成树,然后对于树上 \(2^{|S|-1}\) 种状态,自顶向下确定某行/某列是否翻转(会有两种等效的方式),进一步确定其他所有边的状态(只会有一种)。时间复杂度为 \(O(HW)\)。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=210;
const int maxt=maxn<<1;
const int md=1000000007;
int n,m,i,j,k,p,w,a,b,ans=1,c[4];
int fa[maxt],siz[maxt];
char s[maxn][maxn];
int Find(int x){
if(x==fa[x]) return x;
return fa[x]=Find(fa[x]);
}
inline void Merge(int x,int y){
x=Find(x); y=Find(y);
if(x==y) return;
if(siz[x]>siz[y]) swap(x,y);
fa[x]=y; siz[y]+=siz[x]; siz[x]=1;
}
int main(){
scanf("%d%d",&n,&m);
for(i=1;i<=n;++i) scanf("%s",s[i]+1);
a=(n>>1)+1; b=(m>>1)+1;
if(n&1){
for(i=1;s[a][i]==s[a][m-i+1]&&i<=m;++i);
if(i<=m) ans<<=1;
}
if(m&1){
for(i=1;s[i][b]==s[n-i+1][b]&&i<=n;++i);
if(i<=n) ans<<=1;
}
for(i=1,j=a+b-2;i<=j;++i) fa[i]=i,siz[i]=1;
for(i=1;i<a;++i){
for(j=1;j<b;++j){
char t[4]={s[i][j],s[i][m-j+1],
s[n-i+1][j],s[n-i+1][m-j+1]};
sort(t,t+4); w=24;
for(k=c[p=0]=1;k<4;++k)
w/=(++c[p+=(t[k]!=t[k-1])]);
if(p==3) Merge(i,a+j-1),w=12;
ans=(1LL*ans*w)%md;
memset(c,0,(p+1)<<2);
}
}
for(i=1,j=a+b-2;i<=j;++i)
for(w=siz[i];--w;ans-=((ans<<=1)>=md)*md);
printf("%d\n",ans);
}

例题2:Rotate 3x3

题意

有一个 \(3\times n\) 的网格,第 \(i\) 行第 \(j\) 列的数为 \(3(j-1)+i\)。可以进行若干次操作,每次将某个 \(3\times 3\) 的子矩阵旋转 \(180^{\rm o}\),求能否经过若干次操作得出某个 \(3\times n\) 矩阵。\(n\le 10^5\)。

解法

显然每一列的数不会改变,且已知每一列被翻转的次数的奇偶性,则可以将每一列压成一个数,可以用正负号表示对应的列是否翻转。此时每次操作即为取三个相邻的数一起取反,然后交换两边的数。

显然奇数/偶数位置内部才能进行变换,然后对于某两个数交换位置时,我们可以选择中间的任意一个奇偶性不同的数变号。证明考虑对于两个数 \(i,j\) 和中间奇偶性不同的数 \(k\),可以先将 \(i\) 移动到 \(k-1\) 位置(会使得中间一段数全部变号,且从 \(i+1\) 开始每两个数交换一次;最后整体向左平移),再将 \(j\) 移动到 \(k+1\) 位置(同理,但是整体向右平移);对 \(i,k,j\) 进行操作;最后把 \(i,j\) 移回 \(j,i\) 原来的位置。这种操作覆盖了给出的操作,所以可以表示所有操作。同理可以在不改变其他数的情况下将任意两个奇偶性相同的数变号。端点位置也可以变号,考虑让其变成非端点位置变号再移回去即可。

综上,可以设计出任意一个方案使得每个数到达对应位置,然后判断奇数/偶数位置的负数个数是否均为偶数即可。设计方案时,令 \(r_i\) 为第 \(i\) 行的数的绝对值,然后考虑一张 \(n\) 个点的有向图,每个 \(i\) 向 \(r_i\) 连一条边(显然有向图由若干个简单环组成);则每次对于某个 \(i\ne r_i\) 的 \(i\) 将 \(r_i\) 和 \(r_{r_i}\) 交换位置时,对应在有向图上就是将 \(r_i\) 移出所在的简单环,所以在不超过 \(n\) 次操作后一定可以将每个数换到原来的位置。最后注意在每次操作后维护奇数/偶数位置的负数个数。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=100010;
int n,i,j,x,y,z; bool c[2];
int a[maxn][3],r[maxn];
int main(){
scanf("%d",&n); c[0]=c[1]=1;
for(i=0;i<3;++i)
for(j=1;j<=n;++j)
scanf("%d",a[j]+i);
for(i=1;i<=n;++i){
x=a[i][0];
y=a[i][1];
z=a[i][2];
j=y/3+1;
if(y%3!=2||(j^i)&1||
!((y==x+1&&z==y+1)||
(y==x-1&&z==y-1))){
printf("No\n");
return 0;
}
r[i]=j; c[i&1]^=(y<x);
}
for(i=1;i<=n;++i){
x=!(i&1);
while(j=r[i],i!=j){
swap(r[i],r[j]);
c[x]^=1;
}
}
if(c[0]&&c[1]) printf("Yes\n");
else printf("No\n");
return 0;
}

八、递归定义的运用(再帰的な定義の利用)

在分形相关的题目中,可以将 \(K\) 级分形的对应内容拆成 \(K-1\) 级分形的内容以 DP 计算。

例题:Chaotic Polygons

题意

求 \(n\) 阶谢尔宾斯基三角形(如下)内简单闭合回路数量。\(n\le 10^5\)。

解法

考虑该三角形内的回路有两种:

  • 只经过其中一个 \(n-1\) 阶三角形内的。
  • 同时经过三个 \(n-1\) 阶三角形并绕回来的。

令 \(S_n\) 为 \(n\) 阶三角形的答案,而 \(A_n\) 为从某个顶点到另一个顶点的简单路径数量,则 \(S_n=3S_{n-1}+A_{n-1}^3\)。同时从某个顶点到相邻顶点的简单路径也有两种:

  • 只经过两个 \(n-1\) 阶三角形的。
  • 同时经过三个 \(n-1\) 阶三角形的。

此时 \(A_n=A_{n-1}^3+A_{n-1}^2\)。注意简单回路的限制,路径上不能有环,所以需要减去成环(经过某个顶点两次)的方案数。设 \(B_n\) 为从某个顶点到另一个顶点 且经过第三个顶点 的简单路径数,则要经过某个顶点两次时只能选择在两个 \(n-1\) 阶三角形内强制经过第三个顶点,此时 \(A_n=A_{n-1}^3+A_{n-1}^2-A_{n-1}B_{n-1}^2\),同理 \(B_n=B_{n-1}A_{n-1}^2-B_{n-1}^3\)。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int md=1000000007;
int n,i,s=1,a=2,b,c=1,d;
int main(){
scanf("%d",&n);
for(i=1;i<=n;++i){
b=(1LL*a*a)%md;
d=md-(1LL*c*c+md-b)%md;
s=(1LL*b*a+3LL*s)%md;
a=(1LL*a*d+b)%md;
c=(1LL*c*d)%md;
}
printf("%d\n",s);
}

九、数位 dp(桁 DP について)

例题1:Zig-Zag Numbers

题意

求满足下列条件的十进制数 \(x\) 的个数模 \(10000\):

  • \(x\ge A\) 且 \(x\le B\)。
  • \(x\) 是 \(M\) 的倍数。
  • \(x\) 在十进制表示下,如果位数不为 \(1\),则不能有相邻两位相同,且如果第 \(i+2\) 位大于第 \(i+1\) 位则需要第 \(i+1\) 位小于第 \(i\) 位。

\(A,B\le 10^{500},M\le 500\)。

解法

套路题,记 \(dp_{i,j,0/1,r,0/1}\) 为填好了前 \(i\) 位,第 \(i\) 位为 \(j\),模 \(M\) 的值为 \(r\),是否大于 \(i-1\) 位,当前所填后缀是否大于 \(A-1/B\) 的当前后缀的数的个数即可。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=510;
const int md=10000;
int n,m,i,j,k,p,u,v,c,f,g,ans;
int dp[2][maxn][10][2][2];
char a[maxn],b[maxn];
inline void Add(int &x,int y){x-=((x+=y)>=md)*md;}
int Solve(char *x){
n=strlen(x+1);
if(!n) return 0;
auto X=dp[0],Y=dp[1]; int ret=0;
memset(dp,0,sizeof(dp));
c=x[1]^'0';
for(j=0;j<10;++j) X[j%m][j][j>c][0]=1;
for(j=1;j<10;++j) ret+=X[0][j][0][0];
if(n==1) return ret;
for(j=1;j<10;++j) ret+=X[0][j][1][0];
c=x[2]^'0';
for(j=u=0;j<10;++j){
f=j>c; g=j>=c;
for(k=0;k<j;++k){
v=(u+k)%m; p=k%m;
Add(Y[v][j][f][1],X[p][k][0][0]);
Add(Y[v][j][g][1],X[p][k][1][0]);
}
for(++k;k<10;++k){
v=(u+k)%m; p=k%m;
Add(Y[v][j][f][0],X[p][k][0][0]);
Add(Y[v][j][g][0],X[p][k][1][0]);
}
u+=10;
}
for(j=1;j<10;++j) ret+=Y[0][j][0][0]+Y[0][j][0][1];
if(n==2) return ret;
for(j=1;j<10;++j) ret+=Y[0][j][1][0]+Y[0][j][1][1];
swap(X,Y);
for(i=3,v=100%m;i<=n;++i){
memset(Y,0,sizeof(dp[0]));
c=x[i]^'0';
for(p=0;p<m;++p){
for(j=0,u=p;j<10;++j){
f=j>c; g=j>=c;
for(k=0;k<j;++k){
Add(Y[u][j][f][1],X[p][k][0][0]);
Add(Y[u][j][g][1],X[p][k][1][0]);
}
for(++k;k<10;++k){
Add(Y[u][j][f][0],X[p][k][0][1]);
Add(Y[u][j][g][0],X[p][k][1][1]);
}
u-=((u+=v)>=m)*m;
}
}
for(j=1;j<10;++j){
Add(ret,Y[0][j][0][0]);
Add(ret,Y[0][j][0][1]);
if(i!=n){
Add(ret,Y[0][j][1][0]);
Add(ret,Y[0][j][1][1]);
}
}
v=(10*v)%m; swap(X,Y);
}
return ret;
}
int main(){
scanf("%s%s%d",a+1,b+1,&m);
reverse(a+1,a+strlen(a+1)+1);
reverse(b+1,b+strlen(b+1)+1);
for(i=1;a[i]=='0';++i) a[i]='9';
--a[i]; if(a[i]=='0') a[i]=0;
Add(ans=md-Solve(a),Solve(b));
printf("%d\n",ans); return 0;
}

例题2

题意

有 \(n\) 种硬币,第 \(i\) 种硬币为 \(a_i\) 元。现在每种硬币都有无穷个,求使用这些硬币凑出 \(X\) 元的方案数。\(n\le 20,a_i\le 30,X\le 10^{18}\)。

解法

考虑在每次不只多增加一个硬币而是 \(2^k\) 个(或者是对硬币数量二进制拆分)。此时对于每个 \(k\),每种硬币只能有选择 \(2^k\) 个或者不选择两个选项(可以使用背包处理对应的方案);而在选择后需要当前硬币面额总和和 \(X\) 的后 \(k+1\) 位相等。此时可以对 \(X\) 的每一位使用数位 dp 进行转移,转移时需要维护当前面额除以 \(2^k\) 的商方便找出合法的 dp 值。

十、dp 优化(高速化のテクニック)

1. 前缀和优化(累積和の利用)

2. 数据结构优化(データ構造の利用)

3. 利用之前的数组(配列の使いまわし)

例题:Division into Two

题意

有一个长为 \(n\) 的单增序列 \(a\)。求将其划分为两个子序列 \(x,y\),满足 \(x\) 中任意相邻两项之差不小于 \(A\),\(y\) 中任意相邻两数之差不小于 \(B\) 的方案数。\(n\le 10^5\)。

解法

设 \(dp_{i,j,0/1}\) 为考虑了前 \(i\) 个数,且 \(a_i\) 不在的子序列末尾为 \(a_j\),\(a_i\) 是否在 \(y\) 子序列的方案数。转移有:

\[dp_{i+1,i,0}=dp_{i,0,1}+\sum_{a_{i+1}-a_j\ge A}dp_{i,j,1}\\dp_{i+1,i,1}=dp_{i,0,0}+\sum_{a_{i+1}-a_j\ge B}dp_{i,j,0}\\dp_{i+1,j,0}=[a_{i+1}-a_i\ge A]dp_{i,j,0}\\dp_{i+1,j,1}=[a_{i+1}-a_i\ge B]dp_{i,j,1}
\]

考虑在枚举到 \(a_i\) 时,只会更新 \(dp_{i,i-1}\) 的值为 \(dp_{i-1}\) 的某个前缀和,且如果 \(a_{i+1}-a_i\) 过小则新的 \(dp_{i,0}\sim dp_{i,i-2}\) 会赋为 \(0\),否则新的 \(dp_{i,0}\sim dp_{i,i-2}\) 可以直接赋值为 \(dp_{i-1,0}\sim dp_{i,i-2}\)。此时某个 \(dp_{i,j}\) 被赋为 \(0\) 则 \(\forall k>i,dp_{k,j}\) 都会为 \(0\)。此时在计算 \(dp_i\) 时可以直接使用 \(dp_{i-1}\),维护最后一次被赋为 \(0\) 的对应右端点即可。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=100010;
const int md=1000000007;
int n,i,s0,s1,l0,l1,r0=-1,r1=-1;
int d0[maxn],d1[maxn],t0[maxn],t1[maxn];
long long a,b,w,s[maxn];
inline void Add(int &x,int y){x-=((x+=y)>=md)*md;}
int main(){
scanf("%d%lld%lld%lld",&n,&a,&b,s+1);
s0=s1=t0[0]=t1[0]=1;
for(i=1;i<n;++i){
scanf("%lld",&w); s[i+1]=w;
while(w-s[l0+1]>=b) ++l0;
while(w-s[l1+1]>=a) ++l1;
if(l1>r1) Add(d0[i],t1[min(l1,i-1)]);
if(l0>r0) Add(d1[i],t0[min(l0,i-1)]);
if(w-s[i]<a) r0=i-1,s0=0;
if(w-s[i]<b) r1=i-1,s1=0;
Add(s0,d0[i]); Add(s1,d1[i]);
t0[i]=s0; t1[i]=s1;
}
Add(s0,s1); printf("%d\n",s0);
return 0;
}

4. FFT(高速フーリエ変換)

板子

点此查看代码
void DFT(int *f){
for(i=1;i<t;++i) if(i<r[i]) swap(f[i],f[r[i]]);
for(i=1,le=2;le<=t;i=le,le<<=1){
v=Pow(G,(md-1)/le);
for(j=0;j<t;j+=le){
for(k=0,u=1;k<i;++k,u=(1LL*u*v)%md){
p=(1LL*f[i+j+k]*u)%md;
Add(f[i+j+k]=f[j+k],md-p);
Add(f[j+k],p);
}
}
}
}

属于需要背的知识。原根表。常见的原根形如 \(998244353\) 原根为 \(3\),\(924844033\) 原根为 \(5\)。

5. 高维前缀和/FMT(高速ゼータ変換)

计算 \(f_S=\sum_{T\in S}g_T\) 时,考虑分开处理每一位的贡献,设 \(dp_{i,S}\) 为 \(\sum_{j}f_j\) 且 \(j\) 满足第 \(i\) 位以上部分 \(S,j\) 相等,第 \(i\) 位以下部分 \(j\in S\),则有 \(dp_{i,S}\gets dp_{i-1,S}\);转移时如果 \(S\) 的 \(i\) 位为 \(1\),则需要加上 \(dp_{i,S\mathop{\rm xor} 2^i}\) 部分,表示此时的 \(j\) 可以取 \(i\) 位为 \(0\)。最后目标 dp 值即为 \(f\) 值。

6. FWT(And と Add の畳み込み)

此处用了一种较为清奇(?)的思路。

(1) 或卷积(求 \(C_i=\sum_{j\mathop{\rm or}k=i}A_jB_k\))

考虑多项式乘法的过程:我们会先对序列进行 DFT,然后相乘,最后进行 IDFT。此时可以使用类似的方式,用某种序列表示需要操作的序列。

令 \(o(A)_i=\sum_{x\mathop{\rm or}i=i}A_x\),则 \(o(C)_i=\sum_{(j\mathop{\rm or}k)\mathop{\rm or}i=i}A_jB_k\)。而 \(x\mathop{\rm or}i=i\) 等效于 \(x\) 是 \(i\) 的子集,所以 \((j\mathop{\rm or}k)\mathop{\rm or}i\) 当且仅当 \(j,k\) 均是 \(i\) 的子集,也就是说 \(C_i=A_iB_i\)。求 \(o(A/B)\) 可以使用上述的方法,而从 \(o(C)\) 变回 \(C\) 时,考虑上述 dp 过程的逆过程:在从 \(dp_i\) 推到 \(dp_{i-1}\) 时,如果某个 \(S\) 的第 \(i\) 位为 \(1\) 则 \(dp_{i,S}=dp_{i-1,S}+dp_{i-1,S\mathop{\rm xor}2^i}=dp_{i-1,S}+dp_{i,S\mathop{\rm xor}2^i}\),否则 \(dp_{i,S}=dp_{i-1,S}\),直接 dp 即可。

(2) 与卷积(求 \(C_i=\sum_{j\mathop{\rm and}k=i}A_jB_k\))

仍然可以考虑上述的过程,设 \(a(A)_i=\sum_{x\mathop{\rm and}i=i}A_x\),则同样有 \((j\mathop{\rm and}k)\mathop{\rm and}i=i\) 当且仅当 \(j\mathop{\rm and}i=k\mathop{\rm and}i=i\),所以仍然有 \(a(C)_i=a(A)_ia(B)_i\)。求 \(a(A)\) 时仍然可以使用上面 dp 的思路,但是转移时有 \(dp_{i,S}=dp_{i-1,S}+[S\mathop{\rm and}2^i=0]dp_{i-1,S\mathop{\rm or}2^i}\)。从 \(a(C)\) 推回 \(C\) 同理。

(3) 异或卷积(求 \(C_i=\sum_{j\mathop{\rm xor}k=i}A_jB_k\))

考虑异或的性质,令 \(c(x)\) 为 \(x\) 在二进制表示下的位数,可以发现 \((c(j)+c(k)-c(j\mathop{\rm xor}k))\bmod 2=0\),同理有 \((c(j\mathop{\rm and}i)+c(k\mathop{\rm and}i)-c((j\mathop{\rm xor}k)\mathop{\rm and}i))\bmod 2=0\)。令 \(x_p(A)_i=\sum_{c(j\mathop{\rm and}i)\bmod 2=p}A_j\),则 \(x_p(C)_i=\sum_{j,k\in\{0,1\}}[j\mathop{\rm xor}k=p]x_j(A)_ix_k(B)_i\)。\(x_{0/1}\) 也可以使用上述的 dp 方式计算。不过有更简洁的方式计算:设 \(x(A)_i=x_0(A)_i-x_1(A)_i\),则 \(x(C)_i=x(A)_ix(B)_i\)。同或卷积可以直接将求得的 \(C\) 翻转即可。

(4) 子集卷积(求 \(C_i=\sum_{j\mathop{\rm or}k=i,j\mathop{\rm and}k=0}A_jB_k\))

考虑第二个性质等效于 \(c(j)+c(k)=c(i)\)。可以把所有下标按照 \(c\) 的不同值分开处理,设 \(o_p(A)_i\) 为 \(\sum_{c(j)=p,j\mathop{\rm and}i=i}A_j\),则 \(o_p(C)_i=\sum_{j+k=p}o_j(A)_io_k(B)_i\)(暴力计算即可)。

7. 简单剪枝(簡単な枝刈り)

最新文章

  1. C#递归解决汉诺塔问题(Hanoi)
  2. Azure AD Connect 手动同步
  3. mongodb安装启动遇到的问题
  4. Linux 之创建工作目录-mkdir
  5. UE4 将本地图片转成UTexture2D 在runtime显示
  6. Java ----------- SQL语句总结(更新中。。。。。。)
  7. 【Windows 8 Store App】学习:目录
  8. iOS网络编程笔记——XML文档解析
  9. windows系统局域网内开启远程桌面图解
  10. php函数的种类与调用方法大揭密
  11. MongoDB 的 upsert
  12. C语言结构体变量私有化
  13. java多线程和Calendar(日历)常用API
  14. 数据库存储 datetime,时差问题
  15. jquery Jquery 遍历 获取设置 效果
  16. 如何导出标准模板库(STL)类的实例化和包含STL类对象数据成员的类
  17. 安装 nvm 遇到的坑
  18. (转)Hdmi edid 数据解析
  19. java学习笔记—JDBC1(16)
  20. day2 字典常用的方法

热门文章

  1. 2021.09 ccf csp 第四题 收集卡牌
  2. AWG含义及尺寸电流对照表-转载
  3. Java中Set里remove详解
  4. 【微信公众号】记一次微信活动微信公众号分享没有LOGO的解决心路历程
  5. Linux系统实时监控
  6. Document.createEvent与new Event区别
  7. PHP Array数组
  8. 快速确定execl 列数
  9. Tomcat集群配置--负载均衡
  10. Android使用volley发送带参数的post请求