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\) 的方格矩阵,有些方格是一片水域。你的农村伯伯听说你是学计算机的,给你出了一道题: 他问你:这片农田总共包含了多少个不存在水域的正方形农田。
两个正方形农田不同必须至少包含下面的两个条件中的一条:
-
边长不相等;
-
左上角的方格不是同一方格。
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;
}