组合计数题目总结/题解
G - Takahashi And Pass-The-Ball Game
- 遇到在图或树上多次跳点的题可以优先考虑倍增
- 在计算快速幂底数过大且有模数的情况时,可以考虑先对底数取模
- 此题倍增采用了类似快速幂的思想,可以学习
计算系数
- 二项式定理
combination
- 组合计数问题中,当模数很小时考虑用lucas
F - 1122 Subsequence 2
- 组合计数问题中,在考虑使用范德蒙德卷积优化双重循环时,若下指标和不为定值,可以考虑组合计数的对称性进行转化
F - Many Many Paths
- 组合计数问题中,对于 \(\sum_{i=l}^r\) 这种 \(i\) 不从0开始枚举的式子可以考虑将式子拆成 \(\sum_{i=0}^r-\sum_{i=0}^{l-1}\) 这样
,然后在考虑优化
G - Teleporting Takahashi
- 组合计数问题中,当化简\(\sum_{i=1}^n\sum_{j=1}^m\)这种式子时,考虑将与 \(j\) 无关的部分提出来,再考虑优化
「CodePlus 2018 4 月赛」组合数问题 2
- 组合计数问题中,当比较 \(C_a^b\) 与 \(C_c^d\) 时的大小时,可以比较 \(log_2(a!)-log_2(b!)-log_2((a-b)!)\) 与 \(log_2(c!)-log_2(d!)-log_2((c-d)!)\) 的大小,
这样可以避免组合数算出来太大因比较不能取模而爆炸的问题
「SHOI2015」超能粒子炮・改
- 在组合计数问题中,当模数很小,但要枚举很大的下指标时,可以考虑按照 \(mod/i\) 进行分块计算
- 在组合计数问题中,写像C,A一样的带返回值函数时,一定要注意边界问题,尤其是等于 0 和小于 0 的情况
[清华集训2016] 组合数问题
- 在组合计数问题中,对于考虑\(C_a^b\)是否为k的倍数时,可以考虑lucas定理,将a,b转化为k进制下的数,若有\(a_i<b_i\),即 \(C_{a_i}^{b_i}≡0\pmod p\),即 \(C_a^b\) 是k的倍数
B - 123 Triangle
- 注意观察题目中的一些隐藏条件和性质
F - Maximum Diameter
- 组合计数中,考虑运用吸收恒等式优化式子 \(m\cdot C_n^m=n\cdot C_{n-1}^{m-1}\)
- 构造一棵树使得直径最长,那么这棵树一定是一条链
[USACO 2025 US Open, Gold] Problem 1. Moo Decomposition
- 如果我们正着考虑的话, \(O\) 的数量可能不够 \(M\) 分比如当\(k=2,n=9\)时\(MMOOMOOOO\),这种很难考虑,所以我们考虑正难则反,反着统计 \(O\) 的个数,遇到 \(M\) 则答案乘上\(C_{cnto}^k\),随后\(cnto--\),然后就做完了
- 遇到正着不好做的题可以反着考虑
「NOIP2025」清仓甩卖
- 正难则反,合法方案不好求,我们考虑求不合法的
- 对于性价比最高且\(w_i==2\)的物品,选了一定最优
- 对于\(w_i==1\)的物品,若我们选了它,可能会少选一个\(w_i==2\)的物品,而选择一个\(w_i==1\)的鸡肋物品
- 我们称上述错误中第一件 \(w_i=1\) 的物品是特殊物品,\(w_i=2\) 的物品为正确物品
尝试直接对这个结构进行计数。首先把所有 \(a\) 排序
- 分讨
\(I\) 对于性价比大于 \(y\) 的物品,我们一定会选择。
\(II\) 对于原价已经大于 \(a_y\)的物品,无论价格总会被选,会消耗 \(1\) 或 \(2\) 元。这样的物品有 \(n?y\) 个。
\(III\) 对于原价在\(a_x\)和 \(a_y\)之间的物品,\(w=1\) 时性价比优于 \(y\) 会被选,会消耗 \(0\) 或 \(1\) 元。这样的物品有 \(y?x?1\) 个。
\(IIII\) 对于原价在 \(\frac{a_y}{2}\)和\(a_x\)之间的物品,由之前错解的形态可知一定不会被选,只能 \(w=2\) 并消耗 \(0\) 元。
- 也就是说,上述所有物品中我们会花掉 \(m?2\) 元钱。先扣掉 \(I\) 方案中 1/2 元中的 \(1\) 元,变成在 \(n?x?1\) 个物品中选 \(m?2?(n?y)\) 个花掉 1 元。组合数可以 \(O(1)\) 计算
- 对于原价小于 \(a_x\)的部分,我们只需要让 \(a_p+a_x≥a_y\) 的部分(合法)都不成为 \(z\) 即 \(w=2\),剩余的随便选即可。
- 相当于找到最大的 \(p\) 满足\(a_p+a_x<a_y\) ,前面的方案数即为 \(2^p\)
。\(p\) 显然可以在枚举 \(y\) 时使用双指针求出。
信封问题
- 错排板子
分特产
- 正难则反,考虑容斥
- 考虑枚举有i个人为空,方案数为 \(C_n^i\)
- 考虑枚举到第j个特产,\(n-i\)个人\(a_j\)个特产,小球插板问题,不能为空 \(C_{n-i-a_j-1}^{n-i-1}\)
- 容斥 +零个为空-一个为空+两个为空\(\dots\) + \({-1}^{n-1}*n-1\)个为空,做完了
硬币购物
- 先做一次完全背包,算出所有情况
- 对于每个询问,枚举至少 __\(builtin\)_\(popcount(i)\) 个不合法方案数,先手动选出 \(d_j+1\) 个 \(j\) 硬币,对于剩下的钱数,是完全背包的方案,做容斥即可
集合计数
-
设 \(g_i\) 表示至少 \(i\) 个元素在交集中的方案数,那么\(g_i=C_n^i*(2^{2^{n-i}}-1)\)
-
首先我们需要从 n 个元素中选择 i 个元素,所以有 \(C_n^i\) ,然后对于剩下 \(n?i\) 个元素,我们可以列举出可能存在的集合的可能,也就是 \(2^{n-i}\) 种可能,对于这些集合我们可以选可以不选,但是题目要求至少选一个,所以就是一个 \(2^{n?i}\) 元集去掉空集,然后选的元素与不选的元素之间互有影响所以是乘法原理
-
然后就是去找 \(f\) 与 \(g\) 的关系了。其实对于 g,我们还有另一种求法: \(g_i=\sum_{j=i}^nC_j^if_j\) 。其实就是对于选出子集大小恰为 \(j\) 的方案中再去选出 \(i\) 个,与第一种方法等价。
-
二项式反演可以得到
由于 \(n\) 较大,用扩展欧拉定理处理一下即可
水仙花
- 正难则反
- 对于两个条件都不满足的,显然是 \(a_{i+1}|a_i\)
- 本来考虑做容斥,发现 \(f_i\) 的转移非常困难,所以考虑 \(dp\)
- 设 \(f_{i,j}\) 表示连续不合法长度为 \(i\) ,最后一个数为 \(j\), \(dp_i\) 表示长度为 \(i\) 的总方案数,\(ans_i\) 表示 \(1-i\) 的合法方案
- \(f_{i,j}=\sum_{k=2*j}^m f_{i-1,k}\) (\(k\) 每次增加 \(j\))
- \(dp_i=\sum f_{i,j}\)
- \(ans_i=\sum -1*(-1)^{i-j+1}*ans_j*dp_{i-j+1}\)
- 最后答案就是 \(ans+n\) (别忘了取模)
已经没有什么好害怕的了
- 考虑容斥
- 令 \(k\) 为 \(a_i>b_i\) 的对数,则 \(k=\frac{n+k}{2}\)
- 对 \(a,b\) 数组进行升序排序,\(lst_i\) 记录 \(b\) 数组中最后一个小于 \(a_i\) 的位置
- 设\(dp_{i,j}\)表示前 \(i\) 个数中,有 \(j\) 对 \(a_k>b_k (k\in [1,j-1])\) ,则 \(dp\) 转移式为
- 则 \(g_i=(n-i)!*dp_{n,i}\)
- 最后容斥计算 \(f_k\) 即可
[CF285E]Positions in Permutations
- 和已经没有什么好害怕的了这题思路一样,只是dp转移式发生变化
-设 \(dp_{i,j,0/1,0/1}\)表示在\(1-i\)位置中,选了\(j\) 个完美位置,数\(i\)的选择情况,数\(i+1\)的选择情况。也就是说,我们先只考虑完美位置,剩下的位置不填,就可以保证当前位置要填的数之前没有被填过 - 那么有 \(dp\) 转移式
\(dp[i][j][0][0]+=dp[i-1][j-1][0][0]\)
\(dp[i][j][1][0]+=dp[i-1][j-1][0][1]\)
\(dp[i][j][0][1]+=dp[i-1][j-1][0][0]+dp[i-1][j-1][1][0]\)
\(dp[i][j][1][1]+=dp[i-1][j-1][0][1]+dp[i-1][j-1][1][1]\)
\(dp[i][j][0][0]+=dp[i-1][j][0][0]+dp[i-1][j][1][0]\)
\(dp[i][j][1][0]+=dp[i-1][j][0][1]+dp[i-1][j][1][1]\)
- 初始状态
\(dp[1][0][0][0]=dp[1][1][0][1]=1\)
- 那么 \(g_i=(n-i)!*dp_{n,i,0,0}*dp_{n,i,1,0}\)
- 随后反演即可
「THUPC 2019」过河卒二 / chess
- 此题和AT_DP_W网格2相似
- 对于不经过障碍的方案数不好考虑,那我们可以反向思考,计算出到当前障碍 \(x,y\) 的总方案数减去到先前障碍 \(i,j\) 的方案 \((1<=i<=x,1<=j<=y)\),就是到当前点的合法方案
- 先对所有障碍以横坐标为第一关键字,纵坐标为关键字排序
- 然后考虑 \(dp\) ,设 \(dp_i\) 表示处理到第 \(i\) 个障碍时的方案数,那么有转移式
-
\(C_{x+y-2*i}^{x-i}*C_{x+y-i}^i\)表示先从除了斜着走的总步数 \(x-i+y-i\) 中选择 \(x-i\) 步横着走,在从考虑斜着走的总步数 \(x-i+y-i+i\) 中选择 \(i\) 步斜着走,那么竖着走的自然就选好了
-
由于本题模数很小而 \(n,m\) 很大,所以使用lucas实现组合数
「HAOI2017」方案数
- 此题和 「THUPC 2019」过河卒二 / chess 思路一样,只是跳的条件发生改变
- 上一题是可以右,下,斜着跳,而这题只能从 \(x\) 的子集跳到 \(x\)
- 预处理\(f_{i,j,k}\) 表示 \(x\) 有\(i\) 个 1 ,\(y\) 有\(j\) 个 1,\(z\) 有\(k\) 个 1 的方案数,有转移式
\(f[i][j][k]+=f[l][j][k]*C(i,i-l)\)
\(f[i][j][k]+=f[i][l][k]*C(j,j-l)\)
\(f[i][j][k]+=f[i][j][l]*C(k,k-l)\)
- 然后和上一题思路一样,没了
[NOI Online #2 提高组] 游戏
- 此题是二项式反演,考虑做树上背包来计算 \(g\) 数组,详见代码,不再赘述
- 对于题目中出现 ''恰好'' 这样的字眼,我们可以考虑二项式反演,\(dp\) 计算 \(g\) 数组
[清华集训2012][luoguP5933]串珠子
- 注意到 \(n\) 的范围感人,只有16,考虑状压+容斥
- 你只需要用随便连接的方案数减去不连通方案数就能得出答案,详见代码~
- 对于数据范围小于等于20的题目,我们优先考虑状压或者暴搜
Combinatorics Problem
- 对于 \(C_i^j\) 我们可以尝试将他拆成 \(C_{i-1}^j+C_{i-1}^{j-1}\),然后化简式子
[COCI2019-2020#5] Zapina
设 \(dp[i][j]\) 表示:考虑前 \(i\) 个人,已经分出去 \(j\) 道题的方案数,且这前 \(i\) 个人中没有人开心。
转移时枚举第 \(i\) 个人拿到 \(k\) 道题:
「POI2021 R3」Impreza krasnali 2
- 设 \(h\) 中出现过不同的数的数量为 \(c\),原问题等价于把这 \(c\) 个数放在 \(n\) 个位置上,有些位置空着不放,且 \(i\) 和放 \(a_i\) 的位置距离不超过 \(1\) 的方案数,最后再乘上 \((n?c)!\) 就是答案了
- 容易发现如果 \(a_{i?2}=a_i\) ,则 \(a_i\) 必然放在 \(i?1\) 这个位置上;如果 \(a_{i?1}=a_i\),则 \(a_i\) 必然放在 \(i?1\) 或者 \(i\)
- 考虑 \(dp\) 设只考虑前 \(i\) 个的方案数为\(f_{i,0/1/2/3/4}\)
- \(f_{i,0}\): \(a_i\)放在位置\(i-1\)上,且\(a_{i-1}\)不放在\(a_i\) 后面
- \(f_{i,1}\): \(a_i\)放在位置\(i-1\)上,且\(a_{i-1}\)放在\(i\) 上
- \(f_{i,2}\): \(a_i\)放在位置\(i\)上
- \(f_{i,3}\): \(a_i\)放在位置\(i+1\)上,且\(a_{i-1}\)不放在\(i\) 上
- \(f_{i,4}\): \(a_i\)放在位置\(i+1\)上,且\(a_{i-1}\)放在\(i\) 上
-
那么有转移式
-
若\(a_i==a_{i-1}==a_{i-2}\)
\(f[i][0]=f[i-1][2]\)
-
若\(a_i==a_{i-2}!=a_{i-1}\)
\(f[i][0]=f[i-1][1]\)
\(f[i][1]=f[i-1][3]\)
-
若\(a_i==a_{i-1}!=a_{i-2}\)
\(f[i][0]=f[i-1][2]\)
\(f[i][2]=f[i-1][3]+f[i-1][4]\)
-
若\(a_i!=a_{i-1}!=a_{i-2}\)
\(f[i][0]=f[i-1][0]\)
\(f[i][1]=f[i-1][4]\)
\(f[i][2]=f[i-1][1]+f[i-1][0]+f[i-1][2]\)
\(f[i][3]=f[i-1][3]+f[i-1][4]\)
\(f[i][4]=f[i-1][0]+f[i-1][1]+f[i-1][2]\)
-
最终答案是 \((n-c)!(f_{n,0}+f_{n,1}+f_{n,2})\)
[HAOI2011] Problem c
- 此题是 \(dp\) +组合数学
- 考虑给出的编号大于 \(i\) 的人个数 \(s_i\),若 \(s_i>n-i+1\) 则无解
- 由此设计 \(dp\) 状态,设 \(dp_{i,j}\) 表示剩余的人中编号大于等于 \(i\) 已经确定了 \(j\) 个人的编号的方案数
- 那么有转移式
- 其中 \(j<=n-i+1-s_i\) ,\(i\) 倒序枚举
- 最后输出 \(dp_{1,n-m}\) 即可
- 多测要清空~~
「NOIP2021」数列
- 设 \(dp_{i,j,k,p}\) 表示 \(S\) 从低到高的前 \(i\) 位,已经确定了 \(j\) 个 \(a\) 中的元素,前 \(i\) 位中有 \(k\) 个1 ,要向当前讨论位的下一位进位 \(p\)
- 由于填表太困难,我们考虑刷表
- 考虑从\(f(i,j,k,p)\)往后转移,接下来讨论第i位。假设序列a中有t个元素为i,那么就相当于给S的第i位贡献了t个1,再加上上一位进过来的p个1,总共有t+p个1我们知道,当前位每两个1可以向下一位进一个1。所以\((t+p)\pmod2\)的结果即为全部进位后当前位是否为1。同理,向下一位进的1的个数即为\(\lfloor\frac{t+p}{2}\rfloor\),
因此,\(f(i,j,k,p)\)往后转移的状态应该是
- \(f(i,j,k,p)\)对新状态的贡献应该是
-
初始化\(f(0,0,0,0)=1\)
-
最后统计答案,由于a数组中元素范围为\(0\sim m\),所以s总共看\(m+1\)位(虽然s的总位数可能大于m+1,但后面的是进位的,我们无法枚举遍历),对于j这一维,由于最后要确定n个元素,所以是n,k这一维,应该是\(0\sim K\)之间的(K是输入的),p这一维随便,因为不合法的话f为0,加上不影响答案,注意k加上popcount(p)的值不能大于K
「PA 2020」Malowanie płotu
题意
给定 \(n\) 个栅栏,每个栅栏被分成了 \(m\) 段
求给栅栏涂色并满足以下条件的方案数:
- 对于第 \(i\) 个栅栏的涂色必须是一个区间
- 对于第 \(i\) 个栅栏和第 \(i+1\) 个栅栏的涂色区间交集不能为空
- 对于每个栅栏,必须选定一个区间涂色
答案对给定的 \(mod\) 取模
思路
- 设 \(f_{i,j}\) 表示最后选定的区间为 \([i,j]\)时的方案数(滚动数组),则有转移式
对于不满足条件的方案必须满足以下条件之一
- \(y<i\)
- \(j<x\)
所以有20分代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=3010;
int n,m,mod,f[N][N],dp[N][N],ans;
signed main()
{
cin>>n>>m>>mod;
for(int i=1;i<=m;i++)
for(int j=i;j<=m;j++)
f[i][j]=1;
for(int t=2;t<=n;t++)
{
memset(dp,0,sizeof(dp));
for(int i=1;i<=m;i++)
for(int j=i;j<=m;j++)
for(int x=1;x<=m;x++)
for(int y=x;y<=m;y++)
{
if(y<i||x>j) continue;
dp[i][j]=(dp[i][j]+f[x][y])%mod;
}
for(int i=1;i<=m;i++)
for(int j=i;j<=m;j++)
f[i][j]=dp[i][j];
}
for(int i=1;i<=m;i++)
for(int j=i;j<=m;j++)
ans=(ans+f[i][j])%mod;
cout<<ans;
return 0;
}
复杂度 \(O(nm^4)\) 直接 \(T\) 飞,故容斥:
- 考虑用总方案-不满足条件的,
所以
for(int t=2;t<=n;t++)
{
memset(dp,0,sizeof(dp));
for(int i=1;i<=m;i++)
for(int j=i;j<=m;j++)
{
for(int x=1;x<=m;x++)
for(int y=x;y<=m;y++)
dp[i][j]=(dp[i][j]+f[x][y])%mod;
for(int x=1;x<=i;x++)
for(int y=x;y<i;y++)
dp[i][j]=(dp[i][j]-f[x][y]+mod)%mod;
for(int x=j+1;x<=m;x++)
for(int y=x;y<=m;y++)
dp[i][j]=(dp[i][j]-f[x][y]+mod)%mod;
}
for(int i=1;i<=m;i++)
for(int j=i;j<=m;j++)
f[i][j]=dp[i][j];
}
我们设 \(sl_i=\sum\limits_{1\leq x\leq y \leq i}f_{x,y}\) , \(sr_i=\sum\limits_{i\leq x\leq y\leq m}f_{x,y}\)
那么\(f_{i,j}=sl_m-sl_{i-1}-sr_{j+1}\)
for(int t=2;t<=n;t++)
{
memset(sl,0,sizeof(sl));
memset(sr,0,sizeof(sr));
for(int i=1;i<=m;i++)
for(int x=1;x<=i;x++)
for(int y=x;y<=i;y++)
sl[i]=(sl[i]+f[x][y])%mod;
for(int i=1;i<=m;i++)
for(int x=i;x<=m;x++)
for(int y=x;y<=m;y++)
sr[i]=(sr[i]+f[x][y])%mod;
for(int i=1;i<=m;i++)
for(int j=i;j<=m;j++)
f[i][j]=(sl[m]-sl[i-1]-sr[j+1]+mod)%mod;
}
- 发现若 \(f\) 状态为二维枚举一定是 \(O(m^2)\),考虑去掉一维,进一步优化状态:
设\(F_i\) 表示当前区间右端点为 \(i\) 的所有合法方案之和,\(g_i\) 表示当前区间左端点为 \(i\) 的所有合法方案之和
其中
- \(sl\) 和 \(sr\) 均可以用前缀和维护
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e7+10;
int n,m,mod,F[N],g[N],ans,sl[N],sr[N],ssl[N],ssr[N];
signed main()
{
// freopen("sequence.in","r",stdin);
// freopen("sequence.out","w",stdout);
// cin.tie(0); cout.tie(0);
cin>>n>>m>>mod;
for(int i=1;i<=m;i++) F[i]=i%mod;
for(int i=m;i>=1;i--) g[i]=(m-i+1)%mod;
for(int i=2;i<=n;i++)
{
for(int j=1;j<=m;j++) sl[j]=(sl[j-1]+F[j])%mod;
for(int j=m;j>=1;j--) sr[j]=(sr[j+1]+g[j])%mod;
for(int j=1;j<=m;j++) ssl[j]=(ssl[j-1]+sl[j])%mod;
for(int j=m;j>=1;j--) ssr[j]=(ssr[j+1]+sr[j])%mod;
for(int j=1;j<=m;j++) F[j]=(j*(sl[m]-sr[j+1])-ssl[j-1])%mod;
for(int j=1;j<=m;j++) g[j]=((m-j+1)*(sl[m]-sl[j-1])%mod-ssr[j+1])%mod;
}
for(int i=1;i<=m;i++) ans=(ans+g[i])%mod;
ans=(ans+mod)%mod;
cout<<ans;
return 0;
}
usaco1203. Sleeping in Class
- 考虑将所有 \(a_i\) 加起来,和为 \(sum\) ,枚举分的块数,若 \(sum\%a_i==0\) ,暴力计算次数
- 由于 \(sum\leq1e6\) ,所以因子数量不会超过 \(240\) ,时间复杂度是对的
- 考试的时候A了
循环卷积
- 注意数据范围,虽然 \(n\) 很大,但是\(\sum a_i\leq5000\),\(\sum b_i\leq5000\),说明有很多数为 \(0\) ,对答案没有贡献,所以非0的 \(a_i\) 和 \(b_j\) 匹配上的只有 \(5000^2\) 对,大部分的 \(ci\) 都是要么 \(0\) 和 \(0\) 配对,要么正数 \(a_i\) 和 \(b_i=0\) 配对,要么ai=0和正数bi配对
- 把匹配上的单独更新一下,其他的 \(c_i\) 只不过是\(\max(a_i,b_i)\)而已
- 考试的时候虽然注意到了这个限制,可是没想出来怎么利用,只拿了暴力分
异或平方和
- 比如10100的平方,可以拆成 \(10000^2+100^2+2*10000*100\)
- 用 \(a\) 表示 \(00\) 数量,\(b\) 表示 \(11\) 数量,\(c\) 表示 \(01\) 数量,\(d\) 表示 \(10\) 数量
- 那么 \(a*b+c*d\) 对数字可以异或得到 \(11\),贡献是 \(2*(1<<i)*(1<<j)\)
- 答案就是枚举 \(i,j\) ,计算 \(2*(1<<(i+j))*(a*b+c*d)\) 之和
巧克力
- 注意到 \(n\leq500\) ,考虑区间 \(dp\),注意到正向不好删除,我们考虑倒着添加
- 设 \(dp_{i,j}\) 表示 \(i-1\) 和 \(j+1\) 位置已经放置,区间 \(l,r\) 内部的方案数
- 那么当 \(a_{i-1}==a_{r+1}\) 时 \(dp_{i,j}=0\) ,否则 \(dp_{i,j}=\sum\limits_{k=i+1}^{j-1}dp_{i,k-1}*dp_{k+1,j}*C_{len-1}^{k-i}\),表示只考虑前半段的方案数是 \(dp_{i,k-1}\) ,只考虑后半部分的方案数是 \(dp_{k+1,j}\) ,前半部分和后半部分随意考虑的方案数是 \(C_{len-1,k-i}\),
- 转移式的含义是先考虑从 \(len-1\) 个放置操作中挑出来 \(k-i+1\) 个位置分给前半部分,那两个 \(dp\) 是考虑分给前半部分和后半部分的方案具体要怎么考虑
- 无解情况容易判断
- 初值这块
- 对于没有相邻两个 \(a_i\) 的情况,我们找到 \(a_{i-1}!=a_{i+1}\) 则 \(dp_{i,i}=1\),其他为0
- 对于有相邻两个 \(a_i\) 的情况我们可以考虑对删掉一个,剩下的往前补,这样自然就钦定了最开始必须是从此处开始的,随后的初值和上面的情况一样,当然,对于这种情况,我们输出的时候要乘2,这是因为这两个相邻的 \(a_i\) 可以任意选一个当起点,而且方案数一样
- 考试只考虑到用区间 \(dp\) 但是不知道怎么转移,只打了暴力就放弃了 qwq
Piratski kod
-
新定义
-
定义整块为字符串末尾是两个相邻1的字符串
-
定义散块为整个串中没有相邻的1的字符串
-
-
状态设置
-
\(f_{i,0/1}\) 所有长度为 \(i\) 的散块结尾为0/1的价值和
-
\(num_{i,0/1}\) 长度为 \(i\) 的散块结尾为0/1的个数
-
\(dp_i\) 所有长度为 \(i\) 的整块的价值和
-
\(cnt_i\) 长度为 \(i\) 的整块的个数
-
-
转移
\(num[i][0]=num[i-1][0]+num[i-1][1]\)
\(num[i][1]=num[i-1][0]\)
\(f[i][0]=f[i-1][0]+f[i-1][1]\)
\(f[i][1]=f[i-1][0]+fib[i+1]*num[i−1][0]\)
为什么是i+1呢,因为题面规定是下标+2,而这里是长度,而长度等于下标+1,所以应该是长度+1,即 \(i+1\)
\(cnt[i]=\sum\limits_{j=1}^{i-1}cnt[i-j-1]*num[j][1]\)
\(dp[i]=\sum\limits_{j=1}^{i-1}(dp[i-j-1]*num[j][1]+f[j][1]*cnt[i-j-1])\)
- 答案统计
\(ans[i]=\sum_{j=2}^idp[j]*(num[i-j][0]+num[i-j][1])\)
F - Evil Shortest Path
- 这题其实是最短路
- 对于自环,我们肯定不考虑,因为这样再走一次他自己数字一定更大,而对于重边,我们只考虑权值小的,剩下的撇了
- 这题的难点关键在于对于有多条路径到达同一个点,这时我们一定要同时用这些点进行更新,因为他们的地位是相同的,你不确定用哪一个是最优的,所以我们考虑开一个 \(vector\) 一起更新
- 剩下的就好处理了,存边的时候可以开两维,一维记录 \(x\) ,一维记录 \(v\) ,\(vector\) 里放 \(y\) 方便从小到大枚举,然后做一下 \(bfs\) 就好了
- 赛时我想的是记录某个数对于 \(998244353\) 取模的次数和取模的值,塞入优先队列,可是模完的数你不见的乘 \(10+\) 权值就是对的,所以假了
「JOISC 2018 Day 1」帐篷
- 设 \(dp_{i,j}\) 表示在长宽分别为 \(i,j\) 的矩阵中的合法方案
- 我们按照行来考虑
- 考虑第 \(i\)行 不放置帐篷境况,方案数为 \(dp[i-1][j]\)
- 考虑第 \(i\) 行只放一个帐篷,且这一行没有其他帐篷,那么这个帐篷入口朝哪里都行,方案数为 \(dp[i-1][j-1]*j*4\)
- 考虑第\(i\)行放1个帐篷,且这一列前\(i-1\)个位置也选择一个位置放置帐篷,方案数为 \(dp[i][j]+=dp[i-2][j-1]*j*(i-1)\)
- 考虑第\(i\)行放2个帐篷,那这两列都不能再放帐篷,方案数为\(dp[i][j]+=dp[i-1][j-2]*C_j^2\)
- 最后输出的时候要输出 \(dp[n][m]-1\) ,这是因为题目要求不能一个帐篷也不放
「HAOI2018」苹果树
- 首先期望是假的
- 考虑总方案
- 加入第一个节点时,可能位置数为 \(1\),扣除一个位置,加入了两个子节点的位置
- 加入第二个节点时,可能位置数为 \(2\),扣除一个位置,加入了两个子节点的位置
- 加入第 \(n\) 个节点时,可能位置数为 \(n\) ,扣除一个位置,加入了两个子节点的位置
- 可以发现,总方案数就是 \(n!\)
- 考虑子树内的贡献
- 对于一种方案,第 \(i\) 个点和它的父亲的连边的的贡献为 \(size_i*(n-size_i)\)
- 有了这个贡献后,字数内的点怎么放呢,由于必选节点\(i\),所以要从剩下\(n-(i-1)-1\)个点中选\(size_i -1\)个,方案数为\(C_{n-i}^{size_i-1}\)
- 所以总贡献是他俩相乘
- 考虑子树外贡献
-
在加入\(i\)个点后,可以放节点的位置有\(i+1\)个,但是有两个位置是在子树内的,所以实际上只能放在\(i+1-2\)个位置上
-
对于之后的节点同理,所以有\((i+1-2)\times (i+2-2) \times (i+3-2)\times ....\times (n-size_i+1-2)=\frac{(n-size_i-1)!}{(i-2)!}\)
-
所以子树外的方案数为:
-
- 所以答案就是
「HEOI2014」平衡
- 由于题目给定刻度范围为 \(1\sim2\times n+1\) ,所以我们可以都减去 \(n+1\),刻度就变为\(-n\sim n\) ,由于每个橡皮质量为1,所以我们由杠杆定理可得对于每个选中的刻度 \(a_i\) \(\sum a_i=0\) ,对于每个刻度,把 \(n+1\) 加回来,就是 \(\sum{a_i}=k\times(n+1)\)
- 设 \(dp_{i,j}\) 表示在合法范围内选了 \(i\) 个数,和为 \(j\) ,且满足条件的方案数,则有 \(dp\) 转移式
- 最后输出 \(dp[k][k*(n+1)]\)
[USACO 2020 December, Platinum] Problem 1. Sleeping Cows
-
答案与顺序无关,考虑从小到大排序
-
我们可以把房间和奶牛的匹配想成是括号匹配,奶牛为左括号,房间为右括号
-
一个合法的方案要满足最左侧失配的左括号奶牛在最右侧失配的右括号(房间)右侧
-
于是我们令\(f_{i,j,0/1}\) 表示考虑了序列前\(i\)个元素,有\(j\)个奶牛预定了接下来的房间和它匹配,未出现 / 出现“最左侧失配的奶牛”时的方案
数。分第\(i\)个位置是奶牛还是房间转移 -
奶牛转移
- 预定后方房间,\(f_{i,j,0/1}+=f_{i-1,j-1,0/1}\)
- 成为最左侧失配,\(f_{i,j,1}+=f_{i-1,j,0}\)
- 再次失配,\(f_{i,j,1}+=f_{i-1,j,1}\)
-
房间转移
- 去满足某个房间预定,\(f_{i,j,0/1}=(j+1)\times f_{i-1,j+1,0/1}\)
- 失配,\(f_{i,j,0}+=f_{i-1,j,0}\),这一步保证了答案的正确性
-
最后答案是 \(f_{2\times n,0,0}+f_{2\times n,0,1}\)
-
题库提交的代码将 \(0/1\) 定义反转了!!
[ZJOI2010] 排列计数
- 首先,题意可以转化成在一个有 \(n\) 个节点的(完全)二叉树上,把 \(1\times n\)填入,有多少种方案满足任意一个节点的值都小于其子节点的值
- 考虑 \(dp\) ,设 \(siz_x\) 表示节点 \(x\) 的子树大小,\(dp_x\)表示节点 \(x\) 的合法顺序方案数
- 先来看 \(dp_1\) 如何计算
- 根节点是全局最小值,所以填的数必须是 1
- 在剩下有 \(n-1\) 个数中,随便选 \(siz_2\) 个数,填到左子树里,剩下的数填右子树,共 \(C_{n-1}^{siz_2}\) 种方法
- 选好填的数以后,左右子树的内部有 \(dp_2\) , \(dp_3\) 种方法
- 所以方案数是
- 根据归纳法,可得转移式为
然后把叶子节点的dp值设为1,从下往上遍历即可,就做完了
「PA 2024」Bardzo Ulubiony Ciąg
-
我们发现,顺序对答案没有影响,这是因为 \(b\) 实际上是 \(a\) 所有子区间的排列,所以如果把 \(b\) 重新排列,再求一次三元组的数量实际上与答案是一样的,所以可以把 \((i<j<k)\) 这个条件删掉,实际问题就被转换成了对于所有 \(a\) 的子区间,有多少 \(b_i,b_j,b_k\) 满足 \(b_i+b_j+b_k=0\)
-
原式可以转化成 \(sum[i]-sum[x]+sum[j]-sum[y]+sum[k]-sum[z]=0(x<i,y<j,z<k)\)
-
考虑优化,将上面式子移向得 \(sum[i]+sum[j]-sum[y]=sum[x]-sum[k]+sum[z]\),开桶存等号后面的一坨,\(ans\)直接加桶的数量就行,但由于\(x<i\)所以每次\(ans\)只能加\(x=i-1\)的贡献
-
可是还有两个问题
- 会算重方案,如\(b_1,b_2,b_3\)会算多次
- 而且\(x+1\) 到 \(i,y+1\) 到 \(j,z+1\) 到 \(k\)如果有相同集合则不合法
-
对于解决问题 1 ,若全为合法的方式,则每个方式可能有 \(3!\) 种排列方法,最后乘 \(3!\) 就行。至于为什么是 \(3!\) 这不是3个数的全排列吗三个区间一样适用
-
再看2,每次枚举 \(i,x\) 来确定一个集合,如果有集合相同则应减去,
-
①如果有两个集合相同,钦定 \(j\) 所在集合与 \(i\) 相同,则 \(b_i=b_j\) ,要想满足加加加为0则易得 \(b_k=-2b_i\) ,提前开桶来存区间和,两个集合相同的方案即为区间和为 \(b_k\) 的区间数量,这么多的不合法方案也会向①一样被多算要乘 \(3!\over2!\) ,3个数排列但 \(i,j\) 排列是相同方案要除掉
-
②如果三个集合全相同则\(3b_i=0⇒b_i=0\),同理减去区间和为\(0\)的区间数量乘 \(3!\over3!\) 三的阶乘种算一种
-
D - ~K Perm Counting
- 设 \(f(m)\) 表示先指定 \(m\) 个位置,强制它们满足 \(|P_i-i|=k\),剩下 \(n-m\) 个位置随便排的方案数,那么答案就是
- 原题可以看成一个二分图的形式:(\(n=5,k=1\) 时)

-
左边是排列的编号,右边是权值,那么现在要做的就是连 n 条边,补全这个二分图,使得每个点的度数都是 1。
-
如图,连出上图中的任意一条边都会使得\(|P_i-i|=k\)

-
我们考虑选出一些边,而且任意两条边都不能接在同一个端点上(因为每个点的度数要为 \(1\))。
-
发现图中的边构成了若干条链且互不影响,于是把他们拿出来铺平:

-设 \(dp[i][j][0][0/1]\) 表示已经考虑了前 \(i\) 个点,其中连了 \(j\) 条边,第 \(i−1\) 个点和第 \(i\) 个点之间是/否连边的方案数, 容易得到:
-
另外,我们需要开一个数组判断一下某一个点是否是链的开头
-
按着这个 \(dp\),那么有
(\(tot=2\times n\))
(此篇内容大部分是参考luogu题解所写,但保证本人贡献大于一半)

浙公网安备 33010602011771号