最近在补全提高课所有题目的题解,宣传一下汇总的地方提高课题解补全计划
题目描述
给定一棵包含 $n$ 个结点的 树,以及 树 上的 $n-1$ 条边
我们需要在这 $n$ 个结点中,选择一定数量的结点放上 哨兵
最终要求,树中任意 $n-1$ 条边的左右两端,至少有一个结点上放置了 哨兵
求解一个 方案,该方案满足上述要求,且放置的哨兵数量 最少,输出该 方案 放置的 哨兵数量
分析
这是基础课原题 AcWing 285. 没有上司的舞会
显然我们不能 暴力枚举 树中所有结点 选(1) 与 不选(0) 的状态(时间复杂度 为 $O(2^n)$)
因此,会想到采用 分治 思想:
考虑以结点 $u$ 为 根节点 的子树,该子树所有的 方案,可以由当前结点 放 或 不放哨兵 进行划分
这也是一种 状态机模型,我简略的绘制一下这个状态机:
- 如果当前结点放置了哨兵(1),则该子树中,连向他的边的另一端,可以放置(1),也可以不放置(0)
- 如果当前结点没有放哨兵(0),则该子树中,连向他的边的另一端,必须可以放置(1)
这样就可以轻易想到 状态的定义 了,具体如下:
闫氏DP分析法
状态表示 — $f_{i,1/0}$ 集合: 以结点 $i$ 为 根节点 的子树,在 $i$ 上放置哨兵$(1)$ 和不放哨兵$(0)$ 的方案
状态表示 — $f_{i,1/0}$ 属性: 方案中,放置的哨兵数量 最少
状态计算:
在 $i$ 上放置哨兵$(1)$:
$$ f_{i,1} = \sum_{i_{ch}} \min(f_{i_{ch}~,0}, f_{i_{ch}~,1}) \quad(i_{ch} \in \{ver | ver\text{是 i 的子节点}\}) $$
在 $i$ 上不放哨兵$(0)$:
$$ f_{i,0} = \sum_{i_{ch}} f_{i_{ch}~,1} \quad(i_{ch} \in \{ver | ver\text{是 i 的子节点}\}) $$
Code
时间复杂度: $O(n)$
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1510;
int n;
int h[N], e[N], ne[N], idx;
int f[N][2];
bool not_root[N];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs(int u)
{
f[u][0] = 0, f[u][1] = 1; //initialize
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
dfs(j);
f[u][0] += f[j][1];
f[u][1] += min(f[j][0], f[j][1]);
}
}
int main()
{
while (~scanf("%d", &n))
{
memset(h, -1, sizeof h); idx = 0;
memset(not_root, 0, sizeof not_root);
for (int i = 0; i < n; i ++ )
{
int a, b, siz;
scanf("%d:(%d) ", &a, &siz);
while (siz -- )
{
scanf("%d", &b);
add(a, b);
not_root[b] = true;
}
}
int root = 0;
while (not_root[root]) root ++ ;
dfs(root);
printf("%d\n", min(f[root][0], f[root][1]));
}
return 0;
}
scanf的返回值是输入值的个数,如果没有输入值就是返回-1,-1按位取反结果是0
~按位取反,在数值的二进制表示上,将0变为1,将1变为0
~x=-(x+1)
while(~scanf(“%d”, &n))就是当没有输入的时候退出循环
和while(scanf(“%d”,&n)!=EOF)一个意思
记录下,老是忘了
没看y总视频,看一下彩铅大大题解一下就懂了orz daily~
5
0 1 1
1 1 2
2 0
3 1 2
4 1 3
输出为 1 讲道理应该输出2是否有误?
有个疑问:如果我建立的是无向图,为啥任选一个点做为树根去求解的话,都能得到唯一的答案
因为只要你保证了在处理一个结点前已经用dfs提前把它的孩子结点全部处理完毕了,无论你是从哪个结点开始,实际在程序运作的时候都是从底部往上处理的,答案都是固定的。
只不过输出的时候你需要遍历一遍找到最大值,其实这个最大值对应的节点就是那个根节点
就是说,如果父节点不涂色,一定要要求孩子结点全部涂色吗?父节点的父节点涂色,还有存在一个涂色的孩子,其他孩子不涂色不行吗?
题目理解错了。每条边都要观察,父和子一定要有一个存在的。理解成每一个点都要被观察到了。
对于结点u,和它的结点fa。就不能都不选吗?u的孩子结点都选了,然后fa的父亲都选了。那么u和fa就可以都不涂色了。这样的情况包含在dp里了吗?
那u到fa这条边不能被观察到。
当时理解错了,已经明白了。多谢
%%%
大佬您好,请问为什么我把
scanf("%d:(%d) ", &a, &siz);
换成就会报错呢(非科班,基础不扎实请见谅)
char类型数组存字符串会自动给后面补’\0’,会多占一个位置,字符数组开小了就有问题
明白了,谢谢~
其实是这种情况
13:(1) 4
这样你的a读不到正确的值
Orz
f
为什么不用初始化成无穷大呢?这里是统计总数,不需要初始化无穷大
应该是 放置 吧(
谢谢指出,已更正
彩铅佬,这里写了找root的部分是因为存图是有向图的存法吗,看完题想了个差不多的解,看到个找根的操作突然迷惑qwq。
对的,是因为这题给的是有向图的原因,所以要先找根
存成无向图做也是对的 w