hihocoder 1453 Rikka with Tree (简单DP)

 

描述

众所周知,萌萌哒六花不擅长数学,所以勇太给了她一些数学问题做练习,其中有一道是这样的:

勇太有一棵 n 个节点的以1为根的有根树。现在他可以对这棵树进行若干次操作,每一次操作可以选择树上的一个点然后删掉这个点和它的儿子之间的所有边。

现在勇太想要知道对于每一个 k ∈ [1, n],最少需要多少次操作才能让图中恰好存在 k 个联通块。

当然,这个问题对于萌萌哒六花来说实在是太难了,你可以帮帮她吗?

 

输入

第一行输入一个正整数 n (n ≤ 3000)。

第二行输入 n-1 个整数 fi 表示 i+1 号点的父亲,保证 1 ≤ fi ≤ i。

 

输出

输出 n 个整数,第 i 个数表示 k=i 时的答案,如果无法让图中恰好存在 k 个联通块,则输出-1。

 

样例输入

6
1 2 1 1 2

 

样例输出

0 -1 1 1 -1 2

 

思路:

因为题目中有说明,第二行输入 n-1 个整数 fi 表示 i+1 号点的父亲,因此,我们建的图刚开始一定是一个连通图,所以输出的时候第一个答案一定是0咯!

照着样例画出图,我们可以发现,它本身是一个连通块,而当我们想要将它拆分成两个连通块,需要切除一个出度为1的点,若图中不存在这样的点,则输出-1;想要将图拆分成六个连通块,需要减少五个出度,也就是找最少有几个点的出度和为5,输出的是点的个数。

发现规律之后我们可以建图,采用邻接表存储,每个点的出度即为它所邻接点的个数。

然后在这些数中寻找最小能通过加法组合成 k-1 的最少数的个数便是答案。

 

AC代码:

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include<algorithm>
#include<vector>
using namespace std;

vector<int>G[3005];

int dp[1000000];   //dp[i] 代表组合成i所需要的最少数的个数
int weight[3005],sum;
void init(int n)
{
    dp[0]=1;
    for(int i=1; i<=n; i++)
        for(int z=sum; z>=weight[i]; z--)
            if(dp[z-weight[i]])     //找到一个组合
            {
                if(z-weight[i]==0)dp[z]=1;  //如果该点是独立组合出所需要的值
                else
                {
                    if(dp[z]==0)dp[z]=dp[z-weight[i]]+1;    //如果当前位置原来没有被计算过
                    else dp[z]=min(dp[z],dp[z-weight[i]]+1);    //对已经计算过的位置取最小值
                }
            }
}
int main()
{
    int N,par;
    scanf("%d",&N);
    for(int i=1; i<N; i++)  //有向图邻接表
    {
        scanf("%d",&par);
        G[par].push_back(i+1);
    }
    for(int i=1; i<N; i++)
    {
        weight[i]=G[i].size();
        sum+=weight[i];
    }
    init(N);
    printf("0");
    for(int i=1; i<N; i++)
        printf(" %d",dp[i]?dp[i]:-1);
    printf("\n");
    return 0;
}