不知道几百年前写的计数 dp 博客

发布时间 2023-07-04 09:30:11作者: 椎名真粉

远古抽象博客

计数是真的菜/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;
}