计数题
trick
-
对于要求两个边的和相等问题,可以将一边设为正数,另外一边设为负数,那么两边相等问题就变为两边之和是否等于 0 问题,在通过调整将值域变为权正,然后等于某个确定了的数。这道题就运用了这样的思想。
-
有一个很基础的计数问题:\(\forall i\in[1,n],a_i\ge 0\) 要求 \(\sum_{i=1}^n x_i = m\),这个就可以用插板法做,计算 \(m+n-1\choose n-1\)。
那我如果升级一下,变成 \(0\le a_i\le h\),那么就是在其基础上添加一个容斥,枚举有多少个数比 \(m\) 写成式子就是: -
如果要算 \(S>T\) 的方案数,如果 \(S>T\) 和 \(S<T\) 之间存在比较直接的关系,比如 \(S>T\) 的方案数与 \(S<T\) 的方案数是相同的,我们可以先算出 \(S=T\),再拿总方案数减去后除以 2。
-
我们在很多时候,多换换不同角度思考,说不定会豁然开朗。
-
总是能想到对值来进行 DP,但是为什么会这么小气,不舍得多开几维状态,或者用上辅助数组,这点很重要!!!
题目
XMS2C. 概率(pr) \(\checkmark\)
题目大意
有一个随机生成器,随机生成一个长度为 \(2\times n\),值域为 \([0,m]\) 的正整数序列,要求前 \(n\) 个数比后 \(n\) 个数大的概率。
考场思路(70pts)
考场思路
并没有想到正解那样的转化。
我们发现 \(n\) 个数的和有 \(nm+1\) 种可能值,我们算出每个可能值的方案数,做一遍前缀和,那么前 \(n\) 个数比后 \(n\) 个数大的方案数就是 \(\sum_{i=1}^{nm} f_i\times sum_{i-1}\)。
算方案数用 DP,状态数 \(O(nm)\),转移 \(O(n)\) 次,单次转移 \(O(m)\),可以用前缀和优化到 \(O(1)\),所以复杂度就是 \(O(n^2m)\),理论上很难过 70pts 的点。
但是我们发现,相同的 \(m\),不同的 \(n\) 其实可以离线下来放在一起算,这样就可以减少很多计算,就可以卡过 70pts。
code
int calc(int n,int m,int now){
sum[0]=f[0];
for(int i=1;i<=now;i++) sum[i]=sum[i-1],add(sum[i],f[i]);
int p=0,q=0;
for(int i=1;i<=now;i++) add(p,1ll*f[i]*sum[i-1]%mod);
q=qpow(m+1,2*n);
return 1ll*p*qpow(q,mod-2)%mod;
}
void solve(int m){
int ppp=0;
int now=0;
f[0]=1;
for(int i=1;;i++){
g[0]=f[0];
now=i*m;
for(int j=1;j<=now;j++) g[j]=g[j-1],add(g[j],f[j]);
for(int j=0;j<=now;j++) f[j]=0,add(f[j], g[j]-(j-m-1<0 ? 0:g[j-m-1])+mod);
while(ppp<q[m].size() && q[m][ppp].first==i) res[q[m][ppp].second]=calc(i,m,now),ppp++;
if(ppp>=q[m].size()) return clean(i,m),void();
}
}
解题思路
解题思路
一个常见的转化:由于前 \(n\) 个和后 \(n\) 个其实是本质相同的,于是我们其实可以统计两个和相等的方案数,再拿总方案数减去后除以 2,就是前面大于后面的方案数。
现在的问题就转化为统计前 \(n\) 个和后 \(n\) 个相等的方案数,下面依然是非常经典的转化:
把后 \(n\) 个数的值域变成 \([-m,0]\),前 \(n\) 个数不变,那么问题又转化为这 \(2n\) 个数的和为 0,继续调整后 \(m\) 个数的值域为 \([0,m]\),那么就变成了统计 \(2n\) 个数的和为 \(nm\),这其实就变成了一个经典组合计数问题:
如果没有 \(a_i\le m\) 的限制,那么就是插板法,多了 \(a_i\le m\) 的限制,那么就多加一个容斥就行了。复杂度 \(O(Tn)\)。
code
int C(int n,int m){
if(n<m) return 0;
if(n==m || m==0) return 1;
return frac[n]*inv[m]%mod*inv[n-m]%mod;
}
int qry(int n,int m){
return C(n+m-1,m-1);
}
void solve(){
cin>>n>>m;
int nm=n*m,ans=0;
int eq=0,p=0,q=qpow(m+1,2*n);
for(int i=0;i*(m+1)<=n*m;i++){
int op=i&1?-1:1;
int now=qry(n*m-i*(m+1), 2*n)*C(2*n,i)%mod;
eq=(eq+op*now+mod)%mod;
}
p=(q-eq+mod)%mod*inv[2]%mod;
cout<<p*qpow(q,mod-2)%mod<<"\n";
}
启发:
-
对于要求两个边的和相等问题,可以将一边设为正数,另外一边设为负数,那么两边相等问题就变为两边之和是否等于 0 问题,在通过调整将值域变为权正,然后等于某个确定了的数。这道题就运用了这样的思想。
-
有一个很基础的计数问题:\(\forall i\in[1,n],a_i\ge 0\) 要求 \(\sum_{i=1}^n x_i = m\),这个就可以用插板法做,计算 \(m+n-1\choose n-1\)。
那我如果升级一下,变成 \(0\le a_i\le h\),那么就是在其基础上添加一个容斥,枚举有多少个数比 \(m\) 写成式子就是:
- 如果要算 \(S>T\) 的方案数,如果 \(S>T\) 和 \(S<T\) 之间存在比较直接的关系,比如 \(S>T\) 的方案数与 \(S<T\) 的方案数是相同的,我们可以先算出 \(S=T\),再拿总方案数减去后除以 2。
P10764 [BalticOI 2024] Wall \(\checkmark\)
题目大意
有 \(N\) 个墙,每个墙 \(i\) 的高度可为 \(a_i\) 或 \(b_i\)。对每种可能的高度序列,计算其积水量(每个位置的水位为左右两侧不低于该水位的墙所限制的最大值),求所有可能序列的积水量之和对 \(10^9+7\) 取模。
\(1 \le N \le 5\times10^5,\ 1 \le a_i,b_i \le 10^9\),且 \(a_i \neq b_i\)。
解题思路
我们如果依次考虑墙的情况有些难处理,赛时就卡在这里了,换一种考虑方法,考虑每个位置是否会算在水中。
考虑当前格 \((i,h)\),有个方案系数 \(c_i=[a_i<h]+[b_i<h]\)。那么当前格的 \(方案数=c_i\times 左边存在比 h 大的方案数\times 右边存在比 h 大的方案数\),但是我们能够发现,存在比谁大这个条件比较难处理,容斥一下变成总方案数-不存在比它大的方案数,写成式子就是:
令 \(C(l,r)=\prod_{j=l}^{r} c_j\),原式等于:
累加起来,得到一个 \(O(n^2)\) 的做法:
考虑优化,我们把原来的式子拆开:
然后有
离散化后,从小到大枚举 \(h\),用线段树维护 \(C(l,r)\),\(2^{n-i}C(1,i)\),\(2^{i-1}C(i,n)\),复杂度是 \(O(n\log n)\)。
启发:
- 这道题从点考虑会简单很多,并且加上点容斥,在考场上考虑墙会复杂不少,这启发我们在很多时候,多换换不同角度思考,说不定会豁然开朗。
P10366 [PA 2024] Bardzo Ulubiony Ciąg
题目大意
给定长度为 \(n\) 的整数数组 \(a\),将所有子区间和按“左端点递增,左端点相同则右端点递增”的顺序排成数组 \(b\),求 \(b\) 中满足 \(b_i+b_j+b_k=0\ (i<j<k)\) 的三元组个数。
\(1\le n\le 500,\ |a_i|\le 20000\)。
解题思路
首先很显然可以转化成前缀和,\(s_i-s_{i'}+s_j-s_{j'}+s_k-s_{k'}=0\),还有 \(i'<i,j'<j,k'<k\),先不考虑算重,移一下项,变成 \(s_i-s_{i'}+s_j=s_{j'}-s_k+s_{k'}\),直接枚举,复杂度是 \(O(n^3)\),具体实现时要保证 \(j>j'\)。
考虑一下重复该怎么容斥:
- \(i,j,k\) 互不相同,会算 6 次;
- \(i,j,k\) 有两个相同,另外一个不同,会算 4 次;
- \(i,j,k\) 全部相同,会算 1 次。
所以只需要在枚举算出减去第二种情况和第三种,再除以 6 即可。
P13280 「CZOI-R4」午夜巡游
题目大意
有长度为 \(n\) 的排列 \(p\) 和变量 \(x\),初始 \(x=k\)。进行 \(m\) 次操作,每次 \(x \leftarrow p_x\)。求所有可能的排列 \(p\) 下,操作结束后 \(x\) 的值之和,对 \(998244353\) 取模。
\(1\le T\le 10^3,\ 1\le k\le n\le 10^7,\ 0\le m\le 10^9\)。
解题思路
对于这种排列问题,我们肯定要想到置换环。我们可以分成最终 \(x=k\) 和 \(x\ne k\) 两种情况来考虑,因为这两种统计贡献方式显然不同。
\(x=k\)
设 \(k\) 所在的置换环大小为 \(d\),我们需要满足 \(d|m\),枚举 \(d\),我们计算方案数:即满足 \(p_k\ne k,p_{p_{p_\cdots}}=k\),且中间形成一个环的方案数:
当然还有一种推法,就是选出 \(d-1\) 个后,对这 \(d-1\) 个数进行排序,表示置换环顺序,再乘上其他数的方案数:
所以无论 \(d\) 取值如何,都是 \((n-1)!\),所以总方案数 \(A=因子个数\times (n-1)!\),贡献就是 \(Ak\)。
\(x\ne k\)
总方案是 \(n!-A\),并且对于 \({i\in[1,n]}\),它们对应的方案都是等价的,数量相同。所以贡献为:
是用等差数列求和化简一下,两种情况总答案为
预处理一下阶乘还有欧拉筛因数个数。
时间复杂度:\(O(n+T)\)。
启发:
-
分类讨论思想的重要性,并且要熟练运用情况等价的性质。
-
其实这题可以再次升级一下,求每个时刻的 \(x\) 之和。做法:
\(x\ne k\):直接乘 \(m\) 就好了。
\(x=k\) 需要计算 \(F=\sum_{t=1}^m\sum_{d|t} (n-1)!\),这个交换求和顺序之后用整除分块即可通过。
P15389 k-绍兴序列 / splay \(\checkmark\)
题目大意
长度为 \(n\)、每个元素在 \([0,m]\) 内的非负整数序列称为 \(k\)-绍兴序列,当且仅当存在 \(1\le i<j\le n\) 且 \(a_j\ne0\),使得 \(\frac{a_i}{a_j}<k\);若不存在这样的 \(i,j\)(如全零或 \(n=1\)),则不是 \(k\)-绍兴序列。求 \(k\)-绍兴序列的数量 \(\bmod 10^9+7\)。
\(T\le256,\ n,m,k\le 2\times10^5\)。
解题思路
首先肯定要转化成统计不是 \(k-绍兴序列\) 的数量。特判 \(n=1\) 和 \(k=1\),\(k=1\) 就是上升序列计数,答案是 \(n+m\choose m\)。对于正常的情况,由于我们要保证 \(\forall 1\le i<j\le n,a_i\ge a_jk\),因此,这个非零的数一定是一个前缀,并且最多由 \(\log_km\) 个 ,所以直接对值域进行 DP,进行 \(\log_k m\) 轮,每轮值域 \(\times{1\over k}\),加上前缀和优化。时间复杂度 \(O(m)\)。
启发: 需要通过观察一些性质来简化问题。
#P1099. 图论 (graph) \(\checkmark\)
题目大意
维护带非负边权的无向图,支持加边和询问。询问给定 \(u,v,x,b,c\),问等差数列 \(x,x+b,\dots,x+(c-1)b\) 中有多少个数与某条从 \(u\) 到 \(v\) 的路径长度模 \(m\) 同余。路径可重复经过点或边,不要求简单路径。
\(1\le n,q\le 10^5,\ 1<m\le 10^9,\ 1\le w\le m,\ 0\le x,b<m,\ 1\le c\le 10^9\),操作 2 保证 \(u\ne v\)。
解题思路
首先肯定要判断连通性,用一个并查集即可。考虑没一个连通块。
对于连通块内两个点 \(u,v\),考虑他能凑出什么样的路径长度。我们首先可以在块内跑出生成树,不考虑环我们发现路径长度肯定长成 \(dep_u+dep_v-2dep_{lca}+\sum 2w\),这里的 \(w\) 是连通块内所有边的边权。其实也就是 \(dep_u+dep_v-2dep_{lca}+\sum 2w\)。
如果加上环呢?我们发现每个环我们都可以直接加上,为什么呢?

因为红边我们可以单独跑若干次,比如说 \(m\) 次抵消它的贡献,所以我们对于每个这样的环我们都可以跑任意次,那么答案就变成了 \(dep_u+dep_v+\sum_i 2k_iw+\sum_{x,y存在非树边 w} t_i(dep_x+dep_y+w(x,y))\mod M\),我们移一下项运用裴蜀定理发现最终答案一定形如 \(dep_u+dep_v+kg\),其中 \(g=\gcd(M,2w_1,2w_2,\cdots,dep_x+dep_v+w(x,y),\cdots)\),这些东西是可以动态维护的,如果两个不同连通块加边,我们直接启发式合并,对 \(siz\) 小的重新跑一遍生成树,复杂度就是启发式合并的复杂度,考虑每个点会被合并重构统计多少次,因为每次合并至少使得 \(siz\times 2\),所以不会超过 \(\log_2\) 次,总复杂度就是 \(O(n\log n)\);如果是同一连通块加边,那么就是非树边,那么一定会多一个环,将他统计入 \(g\) 即可。
现在回到问题上,统计等差数列上与其同余的个数,即 \(x+tb= dis_u+dis_v+kg\)。移一下项就是 exgcd 可以解决的题目:
这个显然就是 \(exgcd\) 求出最小的 \(t\) 然后每次增加 \(k\over \gcd(b,g)\),很容易算出有多少个。
总复杂度 \(O(n\log n)\)。
启发:
-
连通性问题肯定要先分成连通块来考虑。
-
发觉题目背后的性质,同余这些肯定要联想到裴蜀定理以及 exgcd 这些的。
#P1018. B. 推箱子(box) \(\checkmark\)
题目大意
有一个 \(n\times m\) 的网格,每格为空地 . 或箱子 #。一次整理操作选择上、下、左、右之一,将所有箱子向该方向推到边界(等价于每列/行箱子数不变,全部移到该方向一端)。给定最终局面 \(T\),求有多少个初始局面 \(S\),使得存在若干次整理操作能从 \(S\) 变为 \(T\)。箱子数不变,允许 \(0\) 次操作,答案对 \(10^9+7\) 取模。
\(1\le n,m\le 10^3\)。
解题思路
现在分类讨论一下,注意由于是统计初始局面,所以容斥掉一些情况,\(a_i\) 表示第 \(i\) 行的箱子数,\(b_i\) 表示列:
不操作:
很显然,答案为 1。
操作一次
以向左移动为例,方案是 \(\prod_{i=1}^n {m\choose a_i}\)。其他方向也都一样,只不过 \(n,m\) 可能互换。
操作两次
以向左、向上为例,其它也是本质相同。答案就是 \(p1+p2-p3\),\(p1\) 是先左再上,\(p2\) 是先上再左,\(p3\) 是重复情况。
\(p1=\prod_{i=1}^n {m\choose a_i}\times {n!\over \prod_{v=0}^m cnt_v}\) 其实就是用上面的先向左之后,得到了若干顶到左端的局面,下一步要准备向上,目前合法局面一定是最终局面 \(n\) 行 \(a_i\) 的一种排列,因为只有这样才能保证向上顶得到的是最终局面,后面那一坨就是可重集的排列数。
\(p2\) 同理。
\(p3={n!\over \prod_{v=0}^m cnt_v}\times {m!\over \prod_{v=0}^n cnt_v}\) 重复的情况其实就是行列都是最终局面的一个排列。
对这三种情况分讨即可,需要判断最终局面是否紧挨着四周。
启发:
- 基本功计数题。
[AGC024E] Sequence Growing Hard \(\checkmark\)
题目大意
有数列组 \((A_0,A_1,\dots,A_N)\),其中 \(A_i\) 是长度 \(i\) 的 \(1\sim K\) 整数序列,\(A_{i-1}\) 由 \(A_i\) 删除一个元素得到,且 \(A_i\) 字典序严格大于 \(A_{i-1}\),求满足条件的数列组数量 \(\bmod M\)。
\(1\le N,K\le 300,\ 2\le M\le 10^9\)。
解题思路
我们发现你要在位置 \(i\) 插入一个数 \(x\),这就要求 \(x>a_i\),因为 \(x=a_i\) 的情况与在相等的 \(a_i\) 连续段后面插上 \(x=a_i\),会算重,所以要求 \(x>a_i\)。
这道题对于下标来进行 DP 几乎是不可做的。于是换一种考虑方式,对值来进行 DP 来插入,我们还关注目前有多少位,所以考虑记 \(f_{i,j}\) 表示当前最大的插入值为 \(i\),\(1\sim i\) 一共插了 \(j\) 位的方案数。转移就是:
\(f_{i,j}\gets f_{i-1,k}\times g_{k,j-k}\)
辅助数组 \(g_{i,j}\) 表示分别在 \(len\in{[0,i]}\) 的时候插入 \(a_{len}\) 个数,最后 \(\sum a_{len}=j\) 的方案数。转移就是:
\(g_{i,j}\gets g_{i-1,k}\times (i+1)^{j-k}\)
初始化 \(g_{0,i}=1,f_{0,0}=1\),答案就是 \(f_{k,n}\)。
总复杂度 \(O(n^2k)\)。
启发:
-
总是能想到对值来进行 DP,但是为什么会这么小气,不舍得多开几维状态,或者用上辅助数组,这点很重要!!!
-
这道题其实也可以从树的角度考虑,记得问问gzj,这个也可以学一下,学习将其转化成树上问题的一种思想。
CF1679F Formalism for Formalism \(\checkmark\)
题目大意
给定 \(n\),把每个 \(0\le x<10^n\) 写成恰好 \(n\) 位十进制串(允许前导零)。给出 \(m\) 个无序数对 \((u,v)\),若十进制串中相邻两位为 \((u,v)\) 或 \((v,u)\),则可交换这两位。若两个数可通过若干次交换互相得到,则称它们等价。求最大 \(k\),使得存在 \(k\) 个互不等价的非负整数 \(x_1,\dots,x_k<10^n\),即求等价类个数,输出其对 \(998244353\) 取模。
\(1\le n\le 50000\),\(0\le m\le 45\)。
解题思路
如果像我一样在考场上考虑如何判断两个字符串等价你就炸了,没有任何前途的做法。
我们要求最后不同的等价类数量,对于等价类相关的问题,我们都可以尝试用代表元的思想!一般来说,代表元都是最小/最大/满足一些特殊性质的。这样有很多好处,最显著的就是去重。
这道题的代表元是等价类中字典序最小的十进制串。可以证明:这样代表元不会记重或者漏记。
那么什么样的字符串是满足成为代表元要求的串呢?考虑我们模拟从左往右填数的过程,在第 \(i\) 位能填 \(x\) 当前仅当不存在一个后缀 \(a_j\sim a_{i-1}\) 满足他们都与 \(x\) 能建立交换关系并且 \(a_j>x\)(因为 \((x,y)\) 中 \(x\neq y\) 所以不可能取等)。
那么我们刻画出了限制就可以进行 DP 计数了,定义 \(f_{i,S}\) 表示填到第 \(i\) 位,当前限制的不能填的数的集合是 \(S\) 的方案数,转移并不难,预处理出 \(to(S,i)\) 表示 \(S\) 集合填了 \(i\) 之后能转移到哪个集合,\(f\) 可以用滚动数组优化。
时间复杂度 \(O(n\times 1024\times 10)\),一定要加上剪枝,也就是如果 \(f_{i,S}\) 是 0 就判掉。
启发
- 代表元思想的应用,求不同的等价类数量可以用选取代表元的思考方式。
U715665 奶龙游戏 \(\checkmark\)
题目大意
有 \(2n\) 只奶龙,大小分别为 \(1,2,\dots,2n\)。等概率随机地把其中 \(n\) 只分给 Alice,其余 \(n\) 只给 Bob(即 Alice 的集合在全部 \(\binom{2n}{n}\) 个 \(n\) 元子集中均匀随机)。
进行 \(n\) 轮比赛。每轮 Bob 先从未使用的奶龙中选一只,Alice 观察到 Bob 的选择后,再从自己未使用的奶龙中选一只;大小更大者赢下本轮,用过的奶龙不能再使用。
对每个 \(k=0,1,\dots,n\),Alice 只有恰好赢 \(k\) 轮才最终获胜,否则 Bob 获胜。双方均采用最优策略:Alice 希望最终恰好赢 \(k\) 轮,Bob 希望阻止。求随机分配下 Alice 最终获胜的概率,并对 \(998244353\) 取模。
输出 \(n+1\) 行,第 \(i\) 行(\(0\le i\le n\))表示 \(k=i\) 时的答案。
\(1\le n\le 2\times 10^6\)。
解题思路
对于一种分配方式,我们考虑可以怎么贡献。由于 Alice 后选,并且知道 Bob 是什么,所以它可以直接确定获胜几场,但是这个获胜肯定是有上下限,我们尝试推出这个上下限先,然后根据上下限让每种方案去贡献,说不定还会有一些神秘性质。
首先假设 Alice 的集合是 \(a\),Bob 是 \(b\),\(a,b\) 都是有序的。我们发现 Alice 要赢得多肯定是找到一个 \(k\),\(\forall i\in [1,k]\),我们的分配满足 \(a_{n-k+i}>b_i\),那么这个最大的 \(k\) 就是 Alice 的上界,因为这样的分配方式是最优的。
同理我们可以找到下界,也就是 Bob 最多赢 \(j\) 场,也就是 Alice 至多 \(n-j\) 场。
我们可以合理猜想一下,这个是一段连续区间,即 \(\forall n-j\le m\le k\) 都能找到一组分配方案使得 Alice 赢 \(m\) 场,这个是因为 \(\forall i\in [1,m],a_{n-m+i}>a_{n-k+i}>b_i\),并且 \(\forall i\in [1,n-m],a_{i}<b_{n-j+i}<b_{m+i}\),故 \(m\) 场可以操控。
我们现在考虑怎么统计每个方案的贡献,每个方案贡献的是一段区间 \([l,r]\)。对于一种划分,都可以转成把数动态插入。
我们模拟插入过程,每次就是将 \(1\sim 2n\) 的 \(2n\) 个数从小到大插入到 \(a,b\) 两个最终长度为 \(n\) 的序列中,当前插入到 \(i\),\(len_a-len_b=c_i\),那么说明 \(b\) 后面会比 \(a\) 多 \(c_i\) 个 \(>i\) 的数,所以 \(a\) 至少输 \(c_i\) 场,容易猜到,\(l=max_{i=1}^{2n} c_i\)。
同理,也可以得到 \(r\)。
于是问题可以得到转化,每次插入一个数,相当于给和 \(+1,-1\),那么对于每个 \(k=0\sim n\),就相当于就是求最后和 \(=0\) 并且过程中不可超过 \(k\),也不可能低于 \(n-k\) 的方案数。
这个形式就可以进一步进行经典的转化,将其转化成坐标轴上,每次 \(x+1\) 或者 \(y+1\),要求走到 \((n,n)\) 且不经过 \(y=x+k\) 和 \(y=x-(n-k)\) 的方案数,发现不存在同时经过这两条线,所以可以分开计算。
接下来就是经典的反射容斥,答案即是 \({2n\choose n}-{2n\choose n-k-1}-{2n\choose k-1}\),最后算概率就除以 \(2n\choose n\) 即可。

浙公网安备 33010602011771号