MX 暑假集训 8.1
ゲンチアナ(Gentiana)- LonePi feat.花隈千冬
いたいのいたいのとんでいけ
痛苦啊痛苦飞走吧
真っ白真綿で抱き占めて
化作纯白的锦缎拥抱你
龙胆花真的很好听!LonePi 我们喜欢你!
组合计数专题。难。
Research Rover
题意
给定一个 \(n\times m\) 的网格图,给定 \(k\) 个位置,这些位置上是黑色网格,其余位置是白色网格,一辆车从 \((1,1)\) 移动到 \((n,m)\),移动过程中 \(x,y\) 坐标都不减,初始有一个电量 \(s\),若碰到一个黑色网格,若当前电量为 \(x\),则电量变成 $\left\lceil \frac{x}{2}\right\rceil $,碰到白色格子电量不变,求最后电量的期望值。
\(1\le n,m,s\le 10^5,0\le k\le 2000\)。
solution
\(k\) 很小,所以考虑对黑色格子 dp,令 \(f_{i,j}\) 表示走到第 \(i\) 个黑色格子,共经过了 \(j\) 个黑色格子的方案数,令 \(p(i,k)\) 为从第 \(i\) 个黑色格子走到第 \(k\) 个黑色格子的方案数,令 \(g(i,k)\) 为从第 \(i\) 个黑色格子走到第 \(k\) 个黑色格子,且中途不碰到任何黑色格子的方案数,那么有转移 \(f_{i,j}\leftarrow f_{k,j-1}g(k,i)\)。
我们发现 \(g(i,k)\) 是很难求出的,考虑求出从 \(i\) 走到 \(k\) 经过了至少一个黑色格子的方案数,再用所有方案减去得到合法方案数。
对于一组点 \(A,B,C\),要处理 \(A\) 向 \(C\) 的转移,\(B\) 为第一次经过的黑色格子,那么对于 \(A\) 到 \(B\) 不经过任何黑色格子的方案数在计算 \(f_B\) 时已经被计入,所以可以得到转移式子 \(f_{i,j}\leftarrow (f_{k,j-1}-f_{k,j})p(k,i)\),其中 \(f_{k,j-1}\) 的意义是 \(k\) 向 \(i\) 转移的总方案数,\(f_{k,j}\) 的意义是,减去所有前面的点 \(f_{A,j-1}\) 经过了点 \(k\) 的所有路径数。\(p(k,i)\) 可以预处理组合数 \(O(1)\) 求出。
这样的转移是 \(O(k^3)\) 的,但是发现最多经过 \(\log s\) 个黑色格子电量就会变成 \(1\),所以只需要计算经过黑色格子个数不超过 \(\log s\) 的部分,将这部分的贡献计入答案,再用所有方案数减去这部分的所有方案数即可得到经过超过 \(\log s\) 个黑色格子的方案数,这部分电量一定为 \(1\),直接计入贡献即可。
时间复杂度 \(O(n+m+k^2\log s)\),实现时可以钦定 \((1,1)\) 和 \((n,m)\) 也是黑色格子,最后得到黑色格子数量减去即可。
Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=2e5+10;
constexpr int inf=1e9+10;
constexpr int mod=1e9+7;
inline int ad(int x,int y) {return ((x+y>=mod)?(x+y-mod):(x+y));}
inline void add(int &x,int y) {x=ad(x,y);}
inline ll qpow(ll a,int b) {
ll res=1;
while (b) {
if (b&1) res=res*a%mod;
a=a*a%mod;b>>=1;
}
return res;
}
inline void read(int &x) {
x=0;
char ch=getchar();
while (ch<48) ch=getchar();
while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}
ll fac[N],inv[N];
inline void init(int n) {
fac[0]=inv[0]=1;
f(i,1,n) fac[i]=fac[i-1]*i%mod;
inv[n]=qpow(fac[n],mod-2);
F(i,n-1,1) inv[i]=inv[i+1]*(i+1)%mod;
}
inline ll C(int n,int m) {
if (n<0||m<0||n<m) return 0;
return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
int n,m,k,s;
int dp[N][23];
struct Node{int x,y;}p[N];
inline bool cmp(Node a,Node b) {return (a.x==b.x)?(a.y<b.y):(a.x<b.x);}
inline int calc(Node a,Node b) {return C(b.x-a.x+b.y-a.y,b.x-a.x);}
int main() {
read(n);read(m);read(k);read(s);init(N-1);
f(i,1,k) read(p[i].x),read(p[i].y);
p[++k]={1,1};p[++k]={n,m};
sort(p+1,p+1+k,cmp);
dp[1][0]=1;
f(i,2,k) f(j,1,i-1) f(d,1,20) add(dp[i][d],1ll*ad(dp[j][d-1],mod-dp[j][d])*calc(p[j],p[i])%mod);
ll sum=0,ans=0,ps=calc(p[1],p[k]);
f(i,1,20) {
ans+=1ll*s*dp[k][i]%mod;
sum+=dp[k][i];
s=(s+1)/2;
}
ans+=ad(ps,mod-sum%mod);
printf("%d\n",ans%mod*qpow(ps,mod-2)%mod);
return 0;
}
[省选联考 2020 A 卷] 组合数问题
题意
给定 \(n,x,p,m\) 和一个 \(m\) 次多项式 \(f(k)\),求:
\(1\le n, x, p \le 10^9, 0\le a_i\le 10^9, 0\le m \le \min(n,1000)\)。
solution
呃呃推式子题。
给出几个关键的式子:
原式可以写成:
由普通幂转下降阶乘幂得:
再将下降阶乘幂和组合数转化一下:
交换一下求和顺序,并把只与 \(i,j\) 有关的项放到前面:
\(i,j\) 是可以枚举的,所以我们要化简这个式子:
令 \(t=k-j\),\(t<0\) 的项均为 \(0\) 得:
将 \(x^j\) 提到前面计算:
这就是一个喜闻乐见的二项式定理了,等于:
整理一下,最后的式子是:
暴力递推第二类斯特林数,暴力枚举 \(i,j\) 即可做到 \(O(m^2)\)。
Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=1000+10;
constexpr int inf=1e9+10;
inline void read(int &x) {
x=0;
char ch=getchar();
while (ch<48) ch=getchar();
while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}
int mod;
inline int ad(int x,int y) {return (x+y>=mod)?(x+y-mod):(x+y);}
inline void add(int &x,int y) {x=ad(x,y);}
inline ll qpow(ll a,int b) {
ll res=1;
while (b) {
if (b&1) res=res*a%mod;
a=a*a%mod;b>>=1;
}
return res;
}
int n,x,m;
int a[N],S[N][N],dn[N];
int main() {
read(n);read(x);read(mod);read(m);
f(i,0,m) read(a[i]);
ll ans=0;
S[0][0]=1;dn[0]=1;
f(j,0,m) {
if (j) dn[j]=1ll*dn[j-1]*(n-j+1)%mod;
ll px=qpow(x+1,n-j)*qpow(x,j)%mod*dn[j]%mod;
f(i,j,m) {
if (i&&j) add(S[i][j]=S[i-1][j-1],1ll*j*S[i-1][j]%mod);
ans+=px*a[i]%mod*S[i][j]%mod;
}
}
printf("%lld\n",ans%mod);
return 0;
}
[HNOI2011] 卡农
题意
有 \(n\) 个音阶,由若干个音阶构成的非空集合称为片段,求有多少种本质不同的大小为 \(m\) 的片段的集合,满足所有音阶出现次数均为偶数。
\(1\le n,m\le 10^6\)。
solution
自己根本不会推。
考虑转化题意,将一个片段写成一个 \(n\) 维向量(也就是二进制数),令全集 \(S={1,2,\dots ,2^n-1}\),一个合法片段对应了集合中的一个元素(对于每个音阶,选中则对应位为 \(1\),否则为 \(0\)),那么答案转化为求 \(\sum\limits_{u\subseteq S}[\left\lvert u\right\rvert =m][\bigoplus\limits_{v\in u} v=0]\),其中 $\oplus $ 表示异或操作。
考虑递推,令 \(f_i\) 表示从 \(S\) 中选出了 \(i\) 个不同的数,满足异或和为 \(0\) 的方案数,为了方便我们先钦定这个集合是有序的,也就是选出了一个序列,由于确定了前 \(i-1\) 个数,由异或和为 \(0\) 就能唯一确定最后一个数,但是这样会有不合法的情况,考虑不合法的情况有哪些:
- 前 \(i-1\) 个数异或和为 \(0\),方案数为 \(f_{i-1}\);
- 前 \(i-1\) 个数的异或和为前 \(i-1\) 个数中的一个,那么除了这个数其他数的异或和为 \(0\),这个数可以在前 \(i-1\) 个位置中的任意一个,且有 \(\left\lvert S\right\rvert-(i-2)\) 种取值,所以方案数为 \(f_{i-2}\times (i-1)\times (\left\lvert S\right\rvert-i+2)\)。
这两种情况包含所有不合法情况且相互不重,所以用总方案数 \(A_{\left\lvert S\right\rvert}^{i-1}\) 减去这两种情况的方案数即可,即:
边界状态 \(f_0=1,f_1=0\),最后要乘上 \(\frac{1}{m!}\) 去掉有序限制,时间复杂度 \(O(m)\)。
Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=1e6+10;
constexpr int mod=1e8+7;
inline int ad(int x,int y) {return ((x+y>=mod)?(x+y-mod):(x+y));}
inline void add(int &x,int y) {x=ad(x,y);}
inline ll qpow(ll a,int b) {
ll res=1;
while (b) {
if (b&1) res=res*a%mod;
a=a*a%mod;b>>=1;
}
return res;
}
int main() {
int n,m;scanf("%d%d",&n,&m);n=qpow(2,n)-1;
static int dp[N],A[N];dp[0]=A[0]=1;
f(i,2,m) add(dp[i]=(A[i-1]=1ll*A[i-2]*(n-i+2)%mod),ad(mod-dp[i-1],mod-(1ll*dp[i-2]*(i-1)%mod*(n-i+2)%mod)));
ll fc=1;f(i,2,m) fc=fc*i%mod;
printf("%d\n",dp[m]*qpow(fc,mod-2)%mod);
return 0;
}

浙公网安备 33010602011771号