题意:给定一规模为N的树,问至少去掉多少条边能得到一个规模为P的子树。
题解:看了一些大牛的题解,很困惑纠结一些问题。
- 最佳答案是否是包括整棵树根节点的一颗子树,
- 动态规划定义时的状态是否是包括子树根节点的解。
对于第一个可以很容易举出反例:
4 1
1 2
1 4
2 3
其代价为1的子树只含有节点3,不包括根节点1。所以最佳子树不一定包括整棵树根节点。
对于第二个问题,直接用动态规划说明。
- 设dp[i][fa][j]为计算到fa的第i个子节点为止产生规模为j的子树的至少需要摧毁的边。这里规模为j的子树是包括节点fa的。
- 转移方程:对于计算到fa的第i个子节点sons[fa][i]的每个j状态可由计算到第fa的第i-1个子节点sons[fa][i-1]的状态求得,dp[i][fa][j] = min{min{dp[i-1][fa][k]+dp[M][sons[fa][i-1]][j-k],0 <= k <= j},dp[i-1][fa][j]+1}。其中M为sons[fa][i-1]的子节点数目。其中前一个比较基于计算到第i个子节点为止的最佳子树包括第i个子节点sons[fa][i],所以枚举可能的规模,第二个比较基于不包括sons[fa][i],所以需要在已经计算的摧毁边上再加一。
- dp数组可以使用滚动数组,去除第一维,但是求枚举状态j的必须用倒序,这样才能使数组使用时不被更新为第i个子节点的新状态。
- 初始化:dp[fa][1] = 0,dp[fa][0] = 1。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
#define INF 0X3F3F3F3F
class solve
{
private:
vector<vector<int> > sons;
char* vis;
int** dp;
int N,P;
int root;
int minCost;
int minRoot;
public:
solve(int n,int p):N(n),P(p)
{
sons.resize(N+1);
vis = new char[N+1];
memset(vis,0,sizeof(char)*(N+1));
dp = new int*[N+1];
minCost = INF;
for(int i = 1;i <= N;i++)
{
dp[i] = new int[N+1];
memset(dp[i],0X3F,sizeof(int)*(N+1));
dp[i][1] = 0;
dp[i][0] = 1;
}
processIn();
for(int i = 1;i <= N;i++)
{
if(!vis[i])
//直到遍历完整棵树
{
root = i;
//最后进入的根节点必定是整棵树的根节点
DFS(i);
}
}
if(minRoot != root)
//如果最小的代价不是以根节点为根的子树,还要加上其与父节点链接的道路需要摧毁
{
minCost++;
}
printf("%d\n",minCost);
}
~solve()
{
vector<vector<int> >().swap(sons);
delete[] vis;
delete[] dp;
}
int processIn();
int DFS(int fa);
};
int solve::DFS(int fa)
{
vis[fa] = 1;
int sonSize = sons[fa].size();
int tmpMin;
for(int i = 0;i < sonSize;i++)
{
int son = sons[fa][i];
if(!vis[son])
{
DFS(son);
}
for(int j = P;j >= 1;j--)
//滚动数组倒序DP,利用上一个子节点DP产生的数据
{
tmpMin = INF;
for(int k = 0;k <= j;k++)
{
tmpMin = min(dp[fa][k]+dp[son][j-k],tmpMin);
}
dp[fa][j] = min(dp[fa][j]+1,tmpMin);
}
dp[fa][0] = dp[fa][1]+1;
}
if(minCost >= dp[fa][P])
//需要加上等号,因为要根节点优先,结果可能有1差距
{
minCost = dp[fa][P];
minRoot = fa;
}
return 0;
}
int solve::processIn()
{
int fa,son;
for(int i = 1;i < N;i++)
{
scanf("%d%d",&fa,&son);
sons[fa].push_back(son);
}
return 0;
}
int main()
{
int n,p;
while(~scanf("%d%d",&n,&p))
{
solve poj_1947(n,p);
}
return 0;
}