POJ 3659 Cell Phone Network / HUST 1036 Cell Phone Network(最小支配集,树型动态规划,贪心)-动态规划做法
POJ 3659 Cell Phone Network / HUST 1036 Cell Phone Network(最小支配集,树型动态规划,贪心)
Description
Farmer John has decided to give each of his cows a cell phone in hopes to encourage their social interaction. This, however, requires him to set up cell phone towers on his N (1 ≤ N ≤ 10,000) pastures (conveniently numbered 1..N) so they can all communicate.
Exactly N-1 pairs of pastures are adjacent, and for any two pastures A and B (1 ≤ A ≤ N; 1 ≤ B ≤ N; A ≠ B) there is a sequence of adjacent pastures such that A is the first pasture in the sequence and B is the last. Farmer John can only place cell phone towers in the pastures, and each tower has enough range to provide service to the pasture it is on and all pastures adjacent to the pasture with the cell tower.
Help him determine the minimum number of towers he must install to provide cell phone service to each pasture.
Input
Line 1: A single integer: N
Lines 2..N: Each line specifies a pair of adjacent pastures with two space-separated integers: A and B
Output
Line 1: A single integer indicating the minimum number of towers to install
Sample Input
5
1 3
5 2
4 3
3 5
Sample Output
2
Http
POJ:https://vjudge.net/problem/POJ-3659
HUST:https://vjudge.net/problem/HUST-1036
Source
最小支配集,树型动态规划,贪心
题目大意
在一棵树上选出最小的点集,使得每个点都满足自己在集合中或相连的点在集合中(一下简称某点被覆盖/被选)
解决思路
这道题的贪心解法在这里,那么本文就讲一讲DP的做法。
首先我们观察,一个点被覆盖只有三种情况:
1.被它自己覆盖
2.被它的儿子节点覆盖
3.被它的父亲覆盖
根据以上的分析,我们就可以令F[u][0],F[u][1],F[u][2]分别表示u在上述的三种情况能得到的最小值。接下来就是推导出动归方程啦!
先来看简单点的两种情况F[u][0]和F[u][2](下面我们均用v来代表u的子节点)
F[u][0]代表的是u点选择u点进行覆盖,那么经过推到我们可得
而F[u][2]则代表的是u被其父节点覆盖,这同样也代表着u不是被选中的点,也就是说u的子节点v的F[v][2]不放入考虑范围(为什么?因为F[v][2]代表的是v被父节点也就是u覆盖啦,但u又不选),那么我们就有
最后就是F[u][1]啦。它代表的是u节点被其子节点覆盖。
稍微缓一下,这里说的覆盖可以>=1个子节点哦!
那么对于F[u][1]来说,它可以从v的哪些状态得到呢?
经过简单的思考,我们得出F[u][1]可以从F[v][1]和F[v][0]得到(为什么不能是F[v][2]呢?前面已经讲过了,既然选择F[u][1]计算,也就意味着u点不被选择去覆盖别的点)。那难道动归方程是这样的吗?
且住,这样不就和F[u][2]一样了吗?
不知道读者有没有发现问题,这样写出的方程在转移时可能出现问题,那就是有可能出现在求和时每一个v都取的是F[v][1],这也就意味着我们最后求出来的F[u][1]代表的解中u的子节点v都没有被选择去覆盖别的点!!!,而这与我们对F[u][1]的定义是矛盾的。
怎么办呢?我们要用一个bool类型的变量flag来记录下F[u][1]是否从F[v][0]这种状态转移过来过,如果没有,则要强制让一个从F[v][1]转移过来的变成从F[v][0]转移过来,并且还要保证尽量小。
最后的问题是,如何强制呢?我们令一个变量inc=min(F[v][0]-F[v][1]),那么如果最后检查flag标记时若发现F[u][1]没有从F[v][0]转移来的历史记录,我们就F[u][1]+=inc就可以啦(这就相当于强制让一个从F[v][1]转移过来的变成从F[v][0]转移过来,并保证了最小)
所以最后F[u][1]的状态转移方程就是
最后的最后要注意的地方就是最后输出答案的时候是min(F[1][0],F[1][1]),而不是min(F[1][0],F[1][1],F[1][2])(你问我为什么,1就是根节点,哪里会被其“父节点”覆盖呢)
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int maxN=10001;
const int inf=147483647;
int n;
vector<int> E[maxN];
int F[maxN][5]={0};
bool vis[maxN];
void dfs(int u);
int main()
{
cin>>n;
for (int i=1;i<n;i++)
{
int x,y;
cin>>x>>y;
E[x].push_back(y);
E[y].push_back(x);
}
memset(vis,0,sizeof(vis));
dfs(1);
cout<<min(F[1][0],F[1][1])<<endl;
return 0;
}
void dfs(int u)
{
F[u][0]=1;
F[u][2]=0;
F[u][1]=0;
vis[u]=1;
bool flag=0;//标记u的子树中计算F[u][1]时是否取过F[v][0]
int inc=inf;
for (int i=0;i<E[u].size();i++)
{
int v=E[u][i];
if (vis[v]==0)
{
dfs(v);
F[u][0]+=min(F[v][0],min(F[v][1],F[v][2]));
F[u][2]+=min(F[v][1],F[v][0]);
if (F[v][0]<=F[v][1])//这里就是对F[u][1]的处理啦,另外注意这里要取等
{
flag=1;
F[u][1]+=F[v][0];
}
else
{
inc=min(inc,(F[v][0]-F[v][1]));
F[u][1]+=F[v][1];
}
}
}
if (flag==0)//若在计算F[u][1]时没有从F[v][0]推导过来的,就要强制将一个转换成从F[v][0]推导过来。
{
F[u][1]+=inc;
}
return;
}