20241004 动态规划选讲

20241004 动态规划选讲

P6669 [清华集训2016] 组合数问题

使用 Lucas 定理:\(C_n^m\equiv C_{\lfloor \frac{n}{p}\rfloor}^{\lfloor \frac{m}{p}\rfloor}C_{n\bmod p}^{m\bmod p}(\bmod p)\)。这等价于将 \(n,m\)\(k\) 进制下的每一位的组合数相乘。要想使 \(k\ |\ C_n^m\),就要存在一位的组合数为 \(0\),也就是 \(n\) 的这一位小于 \(m\) 的这一位。数位 dp 就能求出方案。

#include<bits/stdc++.h>
#define int long long

using namespace std;

inline int read(){
	int w = 1, s = 0;
	char c = getchar();
	for (; c < '0' || c > '9'; w *= (c == '-') ? -1 : 1, c = getchar());
	for (; c >= '0' && c <= '9'; s = 10 * s + (c - '0'), c = getchar());
	return s * w;
}

const int INF = 0x3f3f3f3f3f3f3f3f, MOD = 1e9 + 7;

int T, k, n, m, cnt, f[1000][2][2][2], a[1000], b[1000];

int dp(int dep, bool ok, bool in, bool jm){
	if (dep == cnt + 1) return ok;
	if (~f[dep][ok][in][jm]) return f[dep][ok][in][jm];
	int limi = in ? a[dep] : k - 1, limj = jm ? b[dep] : k - 1, res = 0;
	for (int i = 0; i <= limi; i++){
		for (int j = 0; j <= limj; j++){
			(res += dp(dep + 1, ok | (i < j), in & (i == a[dep]), jm & (j == b[dep]))) %= MOD;
		}
	}
	return f[dep][ok][in][jm] = res;
}

signed main(){
	T = read(), k = read();
	while (T--){
		n = read(), m = read();
		int tmp = max(n, m), nn = n, mm = m; cnt = 0;
		while (tmp) cnt++, tmp /= k;
		for (int i = cnt; i; i--) a[i] = n % k, n /= k;
		for (int i = cnt; i; i--) b[i] = m % k, m /= k;
		memset(f, -1, sizeof(f));
        int ans = dp(1, false, true, true);
        if (mm > nn) ans = (ans - ((nn + 1) % MOD) * ((2 * mm - nn) % MOD) % MOD * ((MOD + 1) / 2) % MOD + MOD) % MOD;
        else ans = (ans - (mm % MOD) * ((mm + 1) % MOD) % MOD * ((MOD + 1) / 2) % MOD + MOD) % MOD;
		cout << ans << endl;
	}
	return 0;
}

P3959 [NOIP2017 提高组] 宝藏

转化一下题意,就是要求出一颗生成树,使得每个点的深度乘上它到父亲的边权的总和最小。注意到 \(n\) 很小,考虑状压 dp。设 \(f_{i,s}\) 表示目前已在生成树中的点集为 \(s\),最大深度为 \(i\) 的最小代价。转移时直接枚举树中下一层选哪些点,这样是 \(O(3^nn)\) 的,再 \(O(n)\) 计算贡献,总复杂度 \(O(3^nn^2)\),常数较小,可以通过。

如何 \(O(n)\) 计算贡献?\(O(2^nn^2)\) 预处理 \(a_{i,s}\) 表示 \(i\) 与点集 \(s\) 相连的边权中的最小值,dp 转移时直接使用即可。

AGC009E Eternal Average

转化一下题意:一颗每个非叶节点都有 \(k\) 个儿子的树,叶节点有 \(n+m\) 个,其中 \(n\) 个权值为 \(0\)\(m\) 个权值为 \(1\),每个非叶节点的权值为儿子权值的平均数,求根节点权值的方案数。

考虑一个深度为 \(dep\) (根的深度为 \(1\))的权值为 \(1\) 的叶节点会对根节点的权值造成什么贡献,发现是 \(\frac{1}{k^{dep}}\)。注意到树的层数不超过 \(t=\frac{n+m-1}{k-1}\),那么将所有点的权值乘以 \(k^t\) 也不会影响答案,这时一个叶子的贡献就为 \(k^{t-dep}\)

由此可以想到,将根的权值看作一个允许前导 \(0\)\(t\) 为非零 \(k\) 进制数,那么一个叶子的贡献就是在这个数的从低到高第 \(t-dep+1\) 位加上一。暂时不考虑产生进位的情况。可以发现在节点无标号的情况下,这个 \(k\) 进制数和 \(k\) 叉树是一一对应的。那么用一个背包统计有多少种这样的 \(k\) 进制数。考虑存在进位会发生什么,就等价于 \(k\) 个权值为 \(1\) 的叶子变成上一层的一个权值为 \(1\) 的叶子,节点个数减少 \(k-1\)。此时,树的最大深度也会减一,也就是 \(k\) 进制数的位数。那么统计答案时,直接枚举产生了几次进位累加即可。\(n,m\) 同阶,则时间复杂度 \(O(n^2)\)

int t = (n + m - 1) / (k - 1);
f[0][0] = 1;
for (int i = 0; i < t; i++)
    for (int j = 0; j <= m; j++)
        for (int v = 0; v < k; v++) (f[i + 1][j + v] += f[i][j]) %= MOD;
for (int i = m; i >= 1; i -= k - 1, t--) (ans += f[t][i]) %= MOD;
posted @ 2024-11-08 15:27  陆羽扬  阅读(1)  评论(0编辑  收藏  举报