DP做题记录(二)(2025.3.24)

ARC168E Subsegments with Large Sums

洛谷 P10432 [JOISC 2024] 滑雪 2 (Day1)

洛谷 P4516 [JSOI2018] 潜入行动

很有意思的一道树形 DP 题目。

我们设 \(dp_{u, i, 0 / 1, 0 / 1}\) 表示在 \(u\) 的子树内,一共安装了 \(i\) 个监听器,\(u\) 节点有无被子树内的节点监听,\(u\) 节点有无安装监听器,且子树内其他节点均被监听的方案数。

我们考虑如何转移。我们不断将 \(u\) 的儿子 \(v\) 的子树的答案合并进以 \(u\) 为根的这棵子树。现在来分类讨论:

  • 合并 \(v\) 这棵子树后,根节点没有被监听,也没有安装监听器,此时只有当合并 \(v\) 之前,根节点没有被监听,也没有安装监听器,且 \(v\) 被监听但没有安装监听器时才可以转移,那么有 \(dp_{u, j + l, 0, 0} = \displaystyle\sum_{j + l \leq k} dp_{u, j, 0, 0} \times dp_{v, l, 0, 0}\)

  • 合并 \(v\) 这棵子树后,根节点被监听了,但没有安装监听器,此时又要分两种情况讨论:

    • 合并 \(v\) 这棵子树前,根节点没有被监听,也没有安装监听器,此时只有可能 \(v\) 上安装了监听器,监听到了 \(u\),而且 \(v\) 被监听了。因此 \(dp_{u, j + l, 1, 0} = \displaystyle\sum_{j + l \leq k} dp_{u, j, 0, 0} \times dp_{v, l, 1, 1}\)

    • 合并 \(v\) 这棵子树前,根节点已经被监听了,但没有安装监听器,此时 \(v\) 可以安装监听器,也可以不安装,但 \(v\) 必须要被监听,那么有 \(dp_{u, j + l, 1, 0} = \displaystyle\sum_{j + l \leq k} dp_{u, j, 0, 0} \times (dp_{v, l, 1, 0} + dp_{v, l, 1, 1})\)

  • 合并 \(v\) 这棵子树后,根节点没有被监听,但安装了监听器,此时只有当合并 \(v\) 之前,根节点没有被监听,但安装了监听器,且 \(v\) 没有安装监听器时才可以转移,此时 \(v\) 可以被监听,也可以不被监听,因此 \(dp_{u, j + l, 0, 1} = \displaystyle\sum_{j + l \leq k} dp_{u, j, 0, 1} \times (dp_{v, l, 0, 0} + dp_{v, l, 1, 0}\)

  • 合并 \(v\) 这棵子树后,根节点被监听了,也安装了监听器,此时又要分两种情况讨论:

    • 合并 \(v\) 这棵子树前,根节点没有被监听,但安装了监听器,此时 \(v\) 节点可以被监听,也可以不被监听,但 \(v\) 一定安装了监听器,此时 \(dp_{u, j + l, 1, 1} = \displaystyle\sum_{j + l \leq k} dp_{u, j, 0, 1} \times (dp_{v, l, 0, 1} + dp_{v, l, 1, 1})\)

    • 合并 \(v\) 这棵子树前,根节点被监听了,也安装了监听器,此时 \(v\) 的状态无论是什么都不会影响根节点,于是 \(dp_{u, j + l, 1, 1} = \displaystyle\sum_{j + l \leq k} dp_{u, j, 1, 1} times (dp_{v, l, 0, 0} + dp_{v, l, 0, 1} + dp_{v, l, 1, 0} + dp_{v, l, 1, 1})\)

我们考虑证明时间复杂度,当要将 \(v\) 的答案合并进 \(u\) 中时,需要 \(O(siz_u \times siz_v)\) 的时间复杂度,而由于至多只能安装 \(k\) 个监听器,那么最坏复杂度就是 \(O(k^2)\),而大小为 \(k\) 的子树只有 \(O \left(\displaystyle\frac{n}{k} \right)\) 个,因此最坏复杂度就是 \(O(nk)\),足以通过此题。

完整代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 9, K = 109, MOD = 1e9 + 7;
struct Edge{
	int v, nex;
} e[N << 1];
int head[N], ecnt;
void addEdge(int u, int v){
	e[++ecnt] = Edge{v, head[u]};
	head[u] = ecnt;
}
int dp[N][K][2][2], siz[N], tmp[N][2][2], n, k;//root, the number of monitors, if root is monitored by nodes in its subtree, if there is a monitor on root
void dfs(int u, int fa){
	siz[u] = 1;
	dp[u][0][0][0] = 1;
	dp[u][1][0][1] = 1;
	for(int i = head[u]; i; i = e[i].nex){
		int v = e[i].v;
		if(v == fa)
			continue;
		dfs(v, u);
		for(int j = 0; j <= min(siz[u], k); j++)
			for(int b1 = 0; b1 <= 1; b1++)
				for(int b2 = 0; b2 <= 1; b2++){
					tmp[j][b1][b2] = dp[u][j][b1][b2];
					dp[u][j][b1][b2] = 0;
				}
		for(int j = 0; j <= min(siz[u], k); j++)//the monitors defore
			for(int l = 0; l <= min(siz[v], k - j); l++){//the monitors which are added now
				dp[u][j + l][0][0] = (dp[u][j + l][0][0] + 1ll * tmp[j][0][0] * dp[v][l][1][0] % MOD) % MOD;
				
				dp[u][j + l][1][0] = (dp[u][j + l][1][0] + 1ll* tmp[j][1][0] * (dp[v][l][1][0] + dp[v][l][1][1]) % MOD) % MOD;
				dp[u][j + l][1][0] = (dp[u][j + l][1][0] + 1ll * tmp[j][0][0] * dp[v][l][1][1] % MOD) % MOD;
				
				dp[u][j + l][0][1] = (dp[u][j + l][0][1] + 1ll * tmp[j][0][1] * (dp[v][l][0][0] + dp[v][l][1][0]) % MOD) % MOD;
				
				dp[u][j + l][1][1] = (dp[u][j + l][1][1] + 1ll * tmp[j][1][1] * (1ll * dp[v][l][1][0] + dp[v][l][1][1] + dp[v][l][0][0] + dp[v][l][0][1]) % MOD) % MOD;
				dp[u][j + l][1][1] = (dp[u][j + l][1][1] + 1ll * tmp[j][0][1] * (dp[v][l][0][1] + dp[v][l][1][1]) % MOD) % MOD;
			}
		siz[u] += siz[v];
	}
}
int main(){
	scanf("%d%d", &n, &k);
	for(int i = 1; i < n; i++){
		int u, v;
		scanf("%d%d", &u, &v);
		addEdge(u, v);
		addEdge(v, u);
	}
	dfs(1, 0);
	printf("%d", (dp[1][k][1][0] + dp[1][k][1][1]) % MOD);
	return 0;
}

洛谷 P1758 [NOI2009] 管道取珠

今天做到的最简单的一题了。

考虑 \(\displaystyle\sum a_i^2\) 的含义是什么,考虑同时进行两个管道取珠游戏,那么第一个游戏有 \(a_i\) 种情况得到第 \(i\) 种序列,第二个游戏也有 \(a_i\) 种情况得到第 \(i\) 种序列,总情况数就是 \(a_i^2\),于是原问题可以转化成两次游戏取出数字相等的方案数,现在就好 DP 了。

我们设 \(dp_{i, j, k, l}\) 表示第一个游戏上方管道选到了第 \(i\) 个,下方管道选到了第 \(j\) 个,第二个游戏上方管道选到了第 \(k\) 个,下方管道选到了第 \(l\) 个,两个游戏取出序列相同的方案数。

考虑转移,分下列 \(4\) 种情况:

  • 第一个游戏上方管子第 \(i + 1\) 个数字恰好等于第二个游戏上方管子第 \(k + 1\) 个,那么此时 \(dp_{i + 1, j, k + 1, l}\) 就加上 \(dp_{i, j, k, l}\)

  • 第一个游戏上方管子第 \(i + 1\) 个数字恰好等于第二个游戏下方管子第 \(l + 1\) 个,那么此时 \(dp_{i + 1, j, k, l + 1}\) 就加上 \(dp_{i, j, k, l}\)

  • 第一个游戏下方管子第 \(j + 1\) 个数字恰好等于第二个游戏上方管子第 \(k + 1\) 个,那么此时 \(dp_{i, j + 1, k + 1, l}\) 就加上 \(dp_{i, j, k, l}\)

  • 第一个游戏下方管子第 \(j + 1\) 个数字恰好等于第二个游戏下方管子第 \(l + 1\) 个,那么此时 \(dp_{i, j + 1, k, l + 1}\) 就加上 \(dp_{i, j, k, l}\)

但这样空间复杂度明显不对,于是考虑优化。首先,由于两个游戏取出序列相同,那么 \(i + j = k + l\),那么 \(l\) 就可以计算出,不必记录。

其次,每一步转移只与这一行与下一行相关,于是可以滚动数组进一步优化,然后这道题就做完了。

完整代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 509, MOD = 1024523;
int n, m, dp[2][N][N];
char ch1[N], ch2[N];
int main(){
	scanf("%d%d", &n, &m);
	scanf("%s%s", ch1 + 1, ch2 + 1);
	dp[0][0][0] = 1;
	for(int i = 0; i <= n; i++){
		int cur = i % 2;
		memset(dp[1 - cur], 0, sizeof(dp[1 - cur]));
		for(int j = 0; j <= m; j++)
			for(int k = 0; k <= i + j; k++){
				int l = i + j - k;
				if(ch1[i + 1] == ch1[k + 1])
					dp[1 - cur][j][k + 1] = (dp[1 - cur][j][k + 1] + dp[cur][j][k]) % MOD;
				if(ch1[i + 1] == ch2[l + 1])
					dp[1 - cur][j][k] = (dp[1 - cur][j][k] + dp[cur][j][k]) % MOD;
				if(ch2[j + 1] == ch1[k + 1])
					dp[cur][j + 1][k + 1] = (dp[cur][j + 1][k + 1] + dp[cur][j][k]) % MOD;
				if(ch2[j + 1] == ch2[l + 1])
					dp[cur][j + 1][k] = (dp[cur][j + 1][k] + dp[cur][j][k]) % MOD;
			}
	}	
	printf("%d", dp[n % 2][m][n]);
	return 0;
}
posted @ 2025-03-24 16:10  Orange_new  阅读(31)  评论(1)    收藏  举报