计数 dp 部分例题(六~十部分)
六、转化求和顺序(線形和への分解)
例题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\) 子序列的方案数。转移有:
\]
考虑在枚举到 \(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. 简单剪枝(簡単な枝刈り)
计数 dp 部分例题(六~十部分)的更多相关文章
- [DP之计数DP]
其实说实在 我在写这篇博客的时候 才刚刚草了一道这样类型的题 之前几乎没有接触过 接触过也是平时比赛的 没有系统的做过 可以说0基础 我所理解的计数dp就是想办法去达到它要的目的 而且一定要非常劲非常 ...
- 面渣逆袭:Java并发六十问,快来看看你会多少道!
大家好,我是老三,面渣逆袭 继续,这节我们来盘一盘另一个面试必问知识点--Java并发. 这篇文章有点长,四万字,图文详解六十道Java并发面试题.人已经肝麻了,大家可以点赞.收藏慢慢看!扶我起来,我 ...
- HDU5800 To My Girlfriend 背包计数dp
分析:首先定义状态dp[i][j][s1][s2]代表前i个物品中,选若干个物品,总价值为j 其中s1个物品时必选,s2物品必不选的方案数 那么转移的时候可以考虑,第i个物品是可选可可不选的 dp[i ...
- CodeForces 176B Word Cut (计数DP)
Word Cut Time Limit:2000MS Memory Limit:262144KB 64bit IO Format:%I64d & %I64u Submit St ...
- HDU4815/计数DP
题目链接[http://acm.hdu.edu.cn/showproblem.php?pid=4815] 简单说一下题意: 有n道题,每到题答对得分为a[ i ],假如A不输给B的最小概率是P,那么A ...
- HDU 6377 度度熊看球赛 (计数DP)
度度熊看球赛 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)Total Subm ...
- 计数dp
计数dp 计数类的$dp$没做过几个,所以之前都放到"思维"标签下了,后来发现原来这属于一类问题啊...搬过来了. 管道取珠:https://www.lydsy.com/Judge ...
- [SDOI2010]地精部落[计数dp]
题意 求有多少长度为 \(n\) 的排列满足 \(a_1< a_2> a_3 < a_4 \cdots\) 或者 $a_1> a_2 < a_3 > a_4\cdo ...
- 【BZOJ】2111: [ZJOI2010]Perm 排列计数 计数DP+排列组合+lucas
[题目]BZOJ 2111 [题意]求有多少1~n的排列,满足\(A_i>A_{\frac{i}{2}}\),输出对p取模的结果.\(n \leq 10^6,p \leq 10^9\),p是素数 ...
- 第三百六十九节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)用Django实现搜索功能
第三百六十九节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)用Django实现搜索功能 Django实现搜索功能 1.在Django配置搜索结果页的路由映 ...
随机推荐
- conda迁移虚拟环境
Conda离线迁移虚拟环境主要是两步: 1 在原环境中打包 2 将打好的包copy到目标环境的指定位置 打包的命令很简单 conda pack -n 虚拟环境名 当试图导出base时,报错了 Cond ...
- Django 初步运行过程分析笔记
2. django运行过程分析第一个过程分析:django启动过程python mangage.py runserver 0.0.0.0:8000这个命令先被python的sys.argv接收起来,保 ...
- Jest - Using test function to test the function
Note: Please check the prev blog to see the jest configuration. calculator.js const plus = (a, b) =& ...
- 初识swoole
环境: 腾讯云服务器 centos7 在安装完swoole服务之后 使用 php -m 查看是否有该组件 确认存在后 在根目录下 创建一个文件夹 当做专门测试swoole使用 如 8 在该文件夹下 ...
- deepinlinux安装golang
Download 从 Google 官方获取安装包,不用FQ直接访问 http://golang.google.cn,选择 Linux 的安装包如 go1.14.linux-amd64.tar.gz ...
- Django Rest Framework中文文档:Serializer relations
这是对DRF官方文档:Serializer relations的翻译,根据个人的理解对内容做了些补充.修改和整理. 一,django模型间的关系 在我们对数据进行建模时,最重要的一点就是根据功能需求分 ...
- Curl 命令举例
curl是一个强大的网络请求lib,虽然强大,但是参数太多哈,不得不吐槽,下面列举一下常用使用方法,供大家CV POST 请求 入门: curl -X POST "http://localh ...
- HBase对表增查操作 API
public class HBaseDML { //静态属性 public static Connection conn = HBaseConnection2.conn; //添加数据 public ...
- 【uboot 】uboot通过tftp下载内核
1.开发板uboot,虚拟机能相互ping通 2.ubuntu搭建好tftp服务器,设置好文件夹,放置好文件 sudo apt install tftpd-hpa //安装服务程序 sudo sys ...
- python3.7与python3.6,python2.7 pyc文件头部差异