不知道几百年前写的计数 dp 博客
远古抽象博客
计数是真的菜/kk,特地总结了一下这几天做的计数 \(dp\).
CF1606E
设 \(f_{i, j}\) 表示当场上还有 \(i\) 个英雄,血量最大值为 \(j\) 且最后无人存活的方案数。
当再进行一轮所有英雄都要寄时:
\(f_{i, j} = j ^ i - (j - 1) ^ i\)
\(j ^ i\) 为所有血量的选择方案, \((j - 1) ^ i\) 为没有人血量 \(j\) 的选择方案,即不合法方案。
当在进行一轮后还有英雄存活时:
枚举一个 \(k\) 表示在进行一轮后剩余存活人数
\(f_{i, j} = f_{k, j - i + 1} \times C ^ {i - k}_{i} \times (i - 1) ^ {i - k}\)
时间复杂度 \(O(n ^ 2 x log_2{n})\)
$code : $
#include <cstdio>
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 507;
const int P = 998244353;
LL f[N][N];
LL c[N][N];
int n, m;
inline void Init()
{
for (int i = 0; i <= n; i ++ )
for (int j = 0; j <= i; j ++ )
if (!j) c[i][j] = 1;
else c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % P;
}
inline LL Qmi(LL a, LL b)
{
LL res = 1, x = a;
while (b)
{
if (b & 1) res = res * x % P;
x = x * x % P;
b >>= 1;
}
return res;
}
int main()
{
scanf("%d%d", &n, &m);
Init();
f[0][0] = 1;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
{
if (i - 1 >= j)
{
f[i][j] = (Qmi(j, i) - Qmi(j - 1, i) + P) % P;
continue;
}
for (int k = 1; k <= i; k ++ )
f[i][j] = (f[i][j] + f[k][j - i + 1] * Qmi(i - 1, i - k) % P * c[i][k] % P) % P;
}
LL res = 0;
for (int i = 1; i <= m; i ++ )
res = (res + f[n][i]) % P;
printf("%lld\n", res);
return 0;
}
P3914 染色计数
样例好水,附一组
$input : $
5 4
3 1 2 3
2 1 2
2 2 3
2 1 4
2 1 3
1 2
1 3
3 4
3 5
$output : $
16
很简单的一道计数题/jy
设 \(f_{u, x}\) 表示当前染色染到了 \(u\) 节点,当前节点是颜色编号为 \(x\) 时的方案数。
转移显然 : \(f_{u, x} = \prod\limits_{son}\sum\limits_{y = 1} ^ {m , y != x} f_{son, y}\)
但是这样做时间复杂度时 \(O(n ^ 3)\) 的。/kk
观察转移方程,发现可以通过预处理总和来将 \(\sum\) 那一层优化掉
$code : $
#include <cstdio>
#include <iostream>
using namespace std;
typedef long long LL;
const int P = 1e9 + 7;
const int N = 5007, M = N * 2;
int e[M], ne[M], h[N], idx;
int n, m;
int f[N][N];
int sum[N];
inline void Init()
{
for (int i = 1; i <= n; i ++ )
h[i] = -1;
}
inline void Add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
inline void Dp(int u, int fa)
{
for (int i = h[u]; i != -1; i = ne[i])
{
int v = e[i];
if (v == fa) continue;
Dp(v, u);
for (int x = 1; x <= m; x ++ )
f[u][x] = (LL)(f[u][x] * (LL)(sum[v] - f[v][x] + P) % P) % P;
}
for (int x = 1;x <= m; x ++ )
sum[u] = (sum[u] + f[u][x]) % P;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ )
{
int x;
scanf("%d", &x);
for (int j = 1; j <= x; j ++ )
{
int y;
scanf("%d", &y);
f[i][y] = 1;
}
}
Init();
for (int i = 1; i < n; i ++ )
{
int u, v;
scanf("%d%d", &u, &v);
Add(u, v), Add(v, u);
}
Dp(1, -1);
LL res = 0;
for (int i = 1; i <= m; i ++ )
res = (res + f[1][i]) % P;
printf("%lld\n", res);
return 0;
}
P6870 【COCI2019-2020#5】 Zapina
题不难,就是我是sb
设 \(f_{i, j}\) 表示考虑到第 \(i\) 个人, 前 \(j\) 道题且有一个人开心的方案数。
让第 \(i\) 个人开心:
\(f_{i, j} = C_{j} ^ {i} \times\)
不让第 \(i\) 个人开心:
代码有点阴间,就不放了/hanx
CF367E
CF1051D
CF425E
注意是集合, 和 zzq 大佬讨论了好半天 我是sb。
设 \(f_{i, j}\) 表示当右端点都小于等于 \(i\) ,\(f(S) = j\) 时的方案数
考虑怎么扩展 \(j\)
枚举上一个线段的右端点 \(k\), 可得转移方程
$f_{i, j} = f_{k, j - 1} \times (2 ^ {i - k} - 1) \times $
写个屁,我不写了c
CF1515E
CF559C
S2OJ 排列题
小 Y 的背包计数问题
P5664 [CSP-S2019] Emiya 家今天的饭
寄,太难了/kk
发现前两个条件很好处理,但是第三个不会/kk
然后看了题解发现如果最多只会有一列不合法,考虑计算不合法状态然后容斥,容斥后就可以单独考虑每一行了。
设 \(f_{i, j, k}\) 表示考虑到第 \(i\) 行,一共选了 \(j\) 道菜,枚举的当前列有 \(k\) 道菜的方案数。
这样做时间复杂度是 \(O(n ^ 3m)\) 的,可以获得 \(84pts\) 的好成绩!
考虑优化,发现我们最后的答案要统计的是 \(k > \lfloor \frac{j}{2} \rfloor\) 的 \(dp\) 结果,我们只关心 \(k\) 与 \(j\) 的关系,这样就可以压掉一位。
至于怎么压,我们把上面的式子变一下形变为 \(2k + n - j > n\)
其中 \(n - j\) 就是我们没有选的行数
那么就有了一种神奇的定义状态方法
我们设不选一行的权值为 \(1\), 选当前列的权值为 \(2\)。 (这里的权值是我自己定义的,权值是根据上面式子的常数设的)
设 \(f_{j, k}\) 表示考虑到第 \(j\) 行, 权值为 \(k\) 的方案数。
枚举一列 \(i\) 可得转移方程
\(f_{j, k} = (f_{j, k} + f_{j - 1, k} \times (cnt_j - a_{j, i})) \mod P\) 选了一个其他列
\(f_{j, k} = (f_{j, k} + f_{j - 1, k - 2} \times a_{j, i}) \mod P\) 选了当前列
\(f_{j, k} = (f_{j, k} + f_{j - 1, k - 1}) \mod P\) 没有选
#include <cstdio>
#include <iostream>
using namespace std;
typedef long long LL;
const int P = 998244353;
const int N = 107, M = 2007;
int a[N][M];
LL cnt[N];
int n, m;
LL f[N][N * 2];
LL res = 1;
inline void Init()
{
for (int i = 0; i <= n; i ++ )
for (int j = 0; j <= n * 2; j ++ )
f[i][j] = 0;
}
int main()
{
freopen("in.in", "r", stdin);
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ )
{
for (int j = 1; j <= m; j ++ )
{
scanf("%d", &a[i][j]);
cnt[i] = (cnt[i] + a[i][j]) % P;
}
res = res * (cnt[i] + 1) % P;
}
res -- ;
for (int i = 1; i <= m; i ++ )
{
Init();
f[0][0] = 1;
for (int j = 1; j <= n; j ++ )
for (int k = 0; k <= j * 2; k ++ )
{
f[j][k] = (f[j][k] + f[j - 1][k] * (cnt[j] - a[j][i])) % P;
if (k > 1) f[j][k] = (f[j][k] + f[j - 1][k - 2] * a[j][i]) % P;
if (k) f[j][k] = (f[j][k] + f[j - 1][k - 1]) % P;
}
for (int i = n + 1; i <= n * 2; i ++ )
res = ((res - f[n][i]) % P + P) % P;
}
printf("%lld\n", res);
return 0;
}
woc之前写的题都好 naive 呀/kk
ARC059F
状态很显然,设 \(f_{i,j}\) 表示用了 \(i\) 次操作,现在串里面有 \(j\) 个数字
发现删掉的数字可以是 \(0\) 或 \(1\),但是最后和串匹配上的必须一样,所以让 \(\times 2\) 的贡献在删去时考虑
注意没有数也可以退格
\(code\)
#include<cstdio>
#include<iostream>
#include<cstring>
namespace IO{
template<typename T> inline void rd(T &x){
x=0;bool f=0;char c=0;
while(c<'0'||c>'9') f|=c=='-',c=getchar();
while('0'<=c&&c<='9') x=x*10+(c^48),c=getchar();
x=f?-x:x;
}
template<typename T,typename ...Args> inline void rd(T &x,Args &...args){rd(x),rd(args...);}
template<typename T> inline void wt(char c,T x){
int stk[114],top=0;
if(x<0) x=-x,putchar('-');
do stk[++top]=x%10,x/=10; while(x);
while(top) putchar(stk[top--]+'0');
putchar(c);
}
template<typename T,typename ...Args> inline void wt(char c,T &x,Args &...args){wt(c,x),wt(c,args...);}
};
using namespace IO;
using namespace std;
const int P=1e9+7;
const int N=5007;
int f[N][N];
char s[N];
int n,m;
int main(){
#ifndef ONLINE_JUDGE
freopen("in.in","r",stdin);
freopen("out.out","w",stdout);
#endif
rd(n);
scanf("%s",s+1),m=strlen(s+1);
f[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<=n;j++) f[i][j]=(f[i-1][max(j-1,0)]+2ll*f[i-1][j+1])%P;
}
wt('\n',f[n][m]);
return 0;
}