最近在补全提高课所有题目的题解,宣传一下汇总的地方提高课题解补全计划
题目描述
给定一棵包含 n 个结点的 树,以及 树 上的 n−1 条边
我们需要在这 n 个结点中,选择一定数量的结点放上 哨兵
最终要求,树中任意 n−1 条边的左右两端,至少有一个结点上放置了 哨兵
求解一个 方案,该方案满足上述要求,且放置的哨兵数量 最少,输出该 方案 放置的 哨兵数量
分析
这是基础课原题 AcWing 285. 没有上司的舞会
显然我们不能 暴力枚举 树中所有结点 选(1) 与 不选(0) 的状态(时间复杂度 为 O(2n))
因此,会想到采用 分治 思想:
考虑以结点 u 为 根节点 的子树,该子树所有的 方案,可以由当前结点 放 或 不放哨兵 进行划分
这也是一种 状态机模型,我简略的绘制一下这个状态机:
- 如果当前结点放置了哨兵(1),则该子树中,连向他的边的另一端,可以放置(1),也可以不放置(0)
- 如果当前结点没有放哨兵(0),则该子树中,连向他的边的另一端,必须可以放置(1)
这样就可以轻易想到 状态的定义 了,具体如下:
闫氏DP分析法
状态表示 — fi,1/0 集合: 以结点 i 为 根节点 的子树,在 i 上放置哨兵(1) 和不放哨兵(0) 的方案
状态表示 — fi,1/0 属性: 方案中,放置的哨兵数量 最少
状态计算:
在 i 上放置哨兵(1):
fi,1=∑ichmin
在 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 c[5]; for(int i=0;i<=4;i++)cin>>c[i]; a=c[0]-'0'; siz=c[3]-'0';
就会报错呢(非科班,基础不扎实请见谅)
char类型数组存字符串会自动给后面补’\0’,会多占一个位置,字符数组开小了就有问题
明白了,谢谢~
其实是这种情况
13:(1) 4
这样你的a读不到正确的值
Orz
f
为什么不用初始化成无穷大呢?这里是统计总数,不需要初始化无穷大
应该是 放置 吧(
谢谢指出,已更正
彩铅佬,这里写了找root的部分是因为存图是有向图的存法吗,看完题想了个差不多的解,看到个找根的操作突然迷惑qwq。
#include<bits/stdc++.h> using namespace std; const int N=2005; int dp[N][2],n,m,T; vector <int> v[N]; void dfs(int x,int fa){ dp[x][0]=0;dp[x][1]=1; for(int i=0;i<v[x].size();i++){ int y=v[x][i]; if(y==fa)continue; dfs(y,x); dp[x][0]+=dp[y][1]; dp[x][1]+=min(dp[y][0],dp[y][1]); } } int main () { while(~scanf("%d",&n)){ for(int i=0;i<n;i++)v[i].clear(); for(int i=1;i<=n;i++){ int x,y,t; scanf("%d:(%d) ",&x,&t); while(t--){ scanf("%d",&y);v[x].push_back(y); v[y].push_back(x); } } dfs(0,-1); cout<<min(dp[0][0],dp[0][1])<<endl; } }
对的,是因为这题给的是有向图的原因,所以要先找根
存成无向图做也是对的 w