POJ 1947--Rebuilding Roads

来源:互联网 发布:封面制作软件下载 编辑:程序博客网 时间:2024/06/06 22:59

题意:给定一规模为N的树,问至少去掉多少条边能得到一个规模为P的子树。

题解:看了一些大牛的题解,很困惑纠结一些问题。

  1. 最佳答案是否是包括整棵树根节点的一颗子树,
  2. 动态规划定义时的状态是否是包括子树根节点的解。
对于第一个可以很容易举出反例:
4 1
1 2
1 4
2 3
其代价为1的子树只含有节点3,不包括根节点1。所以最佳子树不一定包括整棵树根节点。
对于第二个问题,直接用动态规划说明。
  1. 设dp[i][fa][j]为计算到fa的第i个子节点为止产生规模为j的子树的至少需要摧毁的边。这里规模为j的子树是包括节点fa的
  2. 转移方程:对于计算到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],所以需要在已经计算的摧毁边上再加一。
  3. dp数组可以使用滚动数组,去除第一维,但是求枚举状态j的必须用倒序,这样才能使数组使用时不被更新为第i个子节点的新状态。
  4. 初始化:dp[fa][1] = 0,dp[fa][0] = 1。

#include<iostream>#include<cstdio>#include<cstring>#include<vector>using namespace std;#define INF 0X3F3F3F3Fclass 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;}


0 0
原创粉丝点击