2022.7.23测试 —— DP专场

1.AcWing 1077. 皇宫看守

Desctiption

求给定的带权树的符合下面条件的子顶点集合 \(V\)

1. 若点 \(i ∈V\),则所有与 \(i\) 相邻的点\(j\) 可被标号。

2. 任意一个点 \(j\),若 \(j\) 不属于 \(V\),则 \(j\) 可被标号。

3. \(S=∑(k[i],i∈V)\),并且 \(S\) 最小。

Solution

考虑树形 DP,发现如果每个只设置是否放守卫两个状态并不可行,考虑更多的状态。

设以 \(1\) 号点为根, \(f[i][0/1/2]\) 分别表示:

\(f[i][0]\) 表示在 \(i\) 号节点不放守卫,但能够被其父亲节点放的守卫瞭望到的最小花费;

\(f[i][1]\) 表示在 \(i\) 号节点不放守卫,但能够被其至少一个子节点放的守卫瞭望到的最小花费;

\(f[i][2]\) 表示在 \(i\) 号节点放守卫的最小花费。

\(j\)\(i\) 的子节点,考虑转移。

  • 若为 \(f[i][0]\) 情况,说明其父亲节点已经可以管到此点,由于是在回溯中转移,该点的子节点放不放守卫都行,只是因为 \(i\) 不放守卫,不能从 \(f[j][0]\) 转移。

\(\qquad \qquad \qquad \qquad f[i][0] = \sum {\min(f[j][1],f[j][2])}\)

  • 若为 \(f[i][2]\) 情况,说明这个点放了守卫,则子节点任意状态均可转移。

\(\qquad \qquad \qquad \qquad f[i][2] = k[i] + \sum {\min(f[j][0],f[j][1],f[j][2])}\)

  • 若为 \(f[i][1]\) 情况,说明这个点能被它的至少一个子节点管到,我们可以枚举它被哪个子节点 \(j'\) 管到最划算,那个子节点必须从 \(f[j'][2]\) 转移来,其他子节点的情况除不能从 \(f[j][0]\) 转移来外任意。

$\qquad \qquad \qquad f[i][1] = \sum {\min(f[j][1],f[j][2])} - \min(f[j'][1],f[j'][2]) + f[j'][2]) $

最后答案为 \(\min(f[1][1],f[1][2])\)

Code

// by youyou2007 in 2022.
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
#include <stack>
#include <map>
#define REP(i, x, y) for(int i = x; i < y; i++)
#define rep(i, x, y) for(int i = x; i <= y; i++)
#define PER(i, x, y) for(int i = x; i > y; i--)
#define per(i, x, y) for(int i = x; i >= y; i--)
#define lc (k << 1)
#define rc (k << 1 | 1)
using namespace std;
const int N = 1505;
int n;
vector <int> g[N];
int w[N]; 
int b[N];
int f[N][3];
void dfs(int x, int fa)
{
	f[x][2] = w[x];//注意 f[x][2] 转移前要加上该点的值
	int sum = 0;
	for(int i = 0; i < g[x].size(); i++)
	{
		int xx = g[x][i];
		if(xx != fa)
		{
			dfs(xx, x);
			f[x][0] += min(f[xx][1], f[xx][2]);
			f[x][2] += min(f[xx][0], min(f[xx][1], f[xx][2]));
			sum += min(f[xx][1], f[xx][2]);
		}
	}
	int minn = 0x3f3f3f3f;//下面都是处理 f[x][1] 的情况
	for(int i = 0; i < g[x].size(); i++)
	{
		int xx = g[x][i];
		if(xx != fa)
		{ 
			minn = min(minn, sum - min(f[xx][1], f[xx][2]) + f[xx][2]); 
		}
	}
	f[x][1] = minn;
}
int main()
{
	scanf("%d", &n);
	rep(i, 1, n)
	{
		int id, T, y;
		scanf("%d", &id);
		scanf("%d", &w[id]);
		scanf("%d", &T);
		while(T--)
		{
			scanf("%d", &y);
			g[id].push_back(y);
			g[y].push_back(id);
		}
	}
	dfs(1, 0);
	printf("%d", min(f[1][1], f[1][2]));
	return 0;
}

2. 矩形切割

Description

给定一个 \(n \times m\) 的矩形,每次可以横或竖按照整数边切一刀,切到所有都是正方形为止,问最少切出多少个正方形。

\(1 \le n,m \le 100\)

Solution

样例非常凉心地卡掉了你的贪心想法

发现 \(n\)\(m\) 都不大,考虑 DP 递推。

\(f[i][j]\) 表示一个 \(i \times j\) 的矩形最少切出正方形个数,很明显,当 \(i=j\) 时,\(f[i][j]=1\)

我们枚举在哪个位置切下去,最小的正方形个数就是切成两半的个数之和,取最小值即可。

$ \qquad \qquad \qquad f[i][j] = \min(f[i - k1][j] + f[k1][j], f[i][j - k2] + f[i][k2]) (1 \le k1 \le i, 1 \le k2 \le j)$

Code

// by youyou2007 in 2022.
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
#include <stack>
#include <map>
#define REP(i, x, y) for(int i = x; i < y; i++)
#define rep(i, x, y) for(int i = x; i <= y; i++)
#define PER(i, x, y) for(int i = x; i > y; i--)
#define per(i, x, y) for(int i = x; i >= y; i--)
#define lc (k << 1)
#define rc (k << 1 | 1)
using namespace std;
const int N = 105;
int f[N][N];
int n, m;
/*
考虑 f[i][j] 表示长 i,宽 j 的矩形最少砍成几个正方形
*/
int main()
{
	scanf("%d%d", &n, &m);
	memset(f, 0x3f, sizeof f);
	rep(i, 0, min(n, m))
		f[i][i] = 1;
	for(int i = 1; i <= n; i++)
	{
		for(int j = 1; j <= m; j++)
		{
			if(i == j) continue;
			for (int k = 1; k <= i - 1; k++)
			{
				f[i][j] = min(f[i][j], f[i - k][j] + f[k][j]);
			}
			for(int k = 1; k <= j - 1; k++)
			{
				f[i][j] = min(f[i][j], f[i][j - k] + f[i][k]);
			}
		}
	}
	printf("%d", f[n][m]);
	return 0;
}

3.count

Description

你家有一片 \(N \times M\) 农田,将其看成一个 \(N \times M\) 的方格矩阵,有些方格是一片水域。你的农村伯伯听说你是学计算机的,给你出了一道题: 他问你:这片农田总共包含了多少个不存在水域的正方形农田。

两个正方形农田不同必须至少包含下面的两个条件中的一条:

  1. 边长不相等;

  2. 左上角的方格不是同一方格。

Solution

我们需要考虑如何不重复、不遗漏地统计出所有的正方形农田,根据性质1、2,可以考虑以每个点为右下角的正方形个数数量,答案即为所有点数量总和。

现在考虑如何计算正方形个数,发现个数恰好是以其为右下角最大正方形的边长。

直接转化问题,设 \(f[i][j]\) 表示以 \((i,j)\) 为右下角的最大正方形的边长,

由这题启发,则转移方程可以推出:

\(f[i][j] = \min(f[i - 1][j-1],f[i-1][j],f[i][j-1])+1\)

然后最后求出总和即可。

Code

// by youyou2007 in 2022.
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
#include <stack>
#include <map>
#define REP(i, x, y) for(int i = x; i < y; i++)
#define rep(i, x, y) for(int i = x; i <= y; i++)
#define PER(i, x, y) for(int i = x; i > y; i--)
#define per(i, x, y) for(int i = x; i >= y; i--)
#define lc (k << 1)
#define rc (k << 1 | 1)
using namespace std;
const int N = 1005;
int n, m;
int mapp[N][N], f[N][N];
int ans;
int main()
{
	scanf("%d%d", &n, &m);
	rep(i, 1, n)
	{
		string s;
		cin >> s;
		rep(j, 0, m - 1)
		{
			mapp[i][j + 1] = s[j] - '0';
		}
	}
	rep(i, 1, n)
	{
		rep(j, 1, m)
		{
			if(mapp[i][j] == 1)
			{
				f[i][j] = min(f[i - 1][j - 1], min(f[i][j - 1], f[i - 1][j])) + 1;
				ans += f[i][j];		
			}
		}
	}
	printf("%d", ans);
	return 0;
}
posted @ 2022-07-23 23:32  panjx  阅读(47)  评论(1编辑  收藏  举报