2017 Multi-University Training Contest 10 && HDU 6178 Monkeys 【贪心||树形DP】

探讨在一棵树上放置猴子的问题,确保每只猴子至少与另一只猴子相连,寻找最少所需边数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


Monkeys

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit:153428/153428 K (Java/Others)
Total Submission(s): 772    Accepted Submission(s): 252

Problem Description

There is a tree having N vertices. In the tree there are K monkeys (K <= N). Avertex can be occupied by at most one monkey. They want to remove some edges and leave minimum edges, but each monkey must be connected to at least oneother monkey through the remaining edges.
Print the minimum possible number of remaining edges.

 

 

Input

The first line contains an integer T (1 <= T <= 100), the number of testcases.
Each test case begins with a line containing two integers N and K (2 <= K<= N <= 100000). The second line contains N-1 space-separated integers
a1,a2,…,aN−1, it means that there is an edge between vertex ai and vertex i+1 (1 <=ai <= i).

 

 

Output

For each test case, print the minimum possible number of remaining edges.

 

 

Sample Input

2

4 4

1 2 3

4 3

1 1 1

 

 

Sample Output

2

2

 


【题意】


给出一棵有n个节点的树,现在需要你把k只猴子放在节点上,每个节点最多放一只猴子,且要求每只猴子必有一只另外的猴子通过一条边与它相连,问最少用多少条边能达到这个要求。


【思路】


利用贪心的思维,显然我们应该先选择性价比最高的,即一条边连接两个点的情况。计算出这样的边的条数ans,如果ans*2>=k,结果就是(k+1)/2,否则剩下来没有安排的猴子每一只需要多一条边,即结果为ans+k-2 * ans.


【PS】这题卡读入时间,故必须用fread读入挂优化。


方法一:用类拓扑排序的方法,找连接两个度数为1且没用被用过的点的边数,每次找到一条边后注意更新度数。


#include <cstdio>
#include <vector>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
#define mst(a,b) memset((a),(b),sizeof(a))
#define rush() int T;scan_d(T);while(T--)

typedef long long ll;
const int maxn = 100005;
const ll mod = 1e9+7;
const int INF = 0x3f3f3f3f;
const double eps = 1e-9;

int vis[maxn];
vector<int>vec[maxn];

using namespace std;
namespace IO
{
const int U=40*1024*1024;
char buf[U];
int ptr,sz;
void begin()
{
    ptr=0;
    sz=fread(buf,1,U,stdin);
}
template<typename T>
inline bool scan_d (T&t)
{
    while (ptr<sz&&buf[ptr]!='-'&&(buf[ptr]<'0'||buf[ptr]>'9'))ptr++;
    if (ptr>=sz)return false;
    bool sgn = false;
    if (buf[ptr]=='-')sgn = true,ptr++;
    for (t = 0; ptr<sz&&'0'<= buf[ptr] &&buf[ptr] <= '9'; ptr++)
        t=t*10+buf[ptr] - '0';
    if (sgn)t=-t;
    return true;
}
}
using namespace IO;


int main()
{
    int n,k;
    IO::begin();
    rush()
    {
        mst(vis,0);
        scan_d(n);
        scan_d(k);
        for(int i=1; i<=n; i++)
        {
            vec[i].clear();
        }
        for(int i=2; i<=n; i++)
        {
            int x;
            scan_d(x);
            vec[x].push_back(i);
            vec[i].push_back(x);
        }
        queue<int>q;
        for(int i=1; i<=n; i++)
        {
            if(vec[i].size()==1)     //将度数为1的点入队列
            {
                q.push(i);
            }
        }
        int ans=0;
        while(q.size())
        {
            int u=q.front();
            q.pop();
            if(vec[u].size()!=0)
            {
                int v=vec[u][0];
                if(vis[u]==0&&vis[v]==0)
                {
                    ans++;
                    vis[u]=1;
                    vis[v]=1;
                }
                vec[v].erase(remove(vec[v].begin(),vec[v].end(),u),vec[v].end());  //更新度数
                if(vec[v].size()==1)             //将度数为1的点入队列
                    q.push(v);
            }
        }
        //printf("**%d\n",ans);
        if(ans*2>=k)
        {
            printf("%d\n",(k+1)/2);
        }
        else
        {
            ans=ans+k-ans*2;
            printf("%d\n",ans);
        }
    }
    return 0;
}


方法二:树形DP


上面说到,我们尽量要使一个点只匹配一个点,很容易想到二分匹配中的最大匹配数,而最大匹配数即为最小点覆盖数,可用树形DP解决。


我们用dp[i][1]表示选择i这个顶点(初始化dp[root][1]为1)的最小点覆盖数,那么它的儿子节点son[i]可以被选择也可以不被选择。


状态转移方程为


dp[i][1]+=min(dp[son[i]]][0],dp[son[i]]][1]).


用dp[i][0]表示不选择i这个顶点(初始化dp[root][0]为0)的最小点覆盖数,显然它的儿子节点son[i]就必须被选择了。


状态转移方程为


dp[i][0]+=dp[son[i]]][1].


最终求出最小点覆盖数(最大匹配数)即为min(dp[1][0],dp[1][1]),也就求出了ans,然后跟上面的步骤相同。


#include <cstdio>
#include <vector>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
#define mst(a,b) memset((a),(b),sizeof(a))
#define rush() int T;scan_d(T);while(T--)

typedef long long ll;
const int maxn = 100005;
const ll mod = 1e9+7;
const int INF = 0x3f3f3f3f;
const double eps = 1e-9;

int n,k;
int dp[maxn][2];
vector<int>vec[maxn];

using namespace std;
namespace IO
{
const int U=40*1024*1024;
char buf[U];
int ptr,sz;
void begin()
{
    ptr=0;
    sz=fread(buf,1,U,stdin);
}
template<typename T>
inline bool scan_d (T&t)
{
    while (ptr<sz&&buf[ptr]!='-'&&(buf[ptr]<'0'||buf[ptr]>'9'))ptr++;
    if (ptr>=sz)return false;
    bool sgn = false;
    if (buf[ptr]=='-')sgn = true,ptr++;
    for (t = 0; ptr<sz&&'0'<= buf[ptr] &&buf[ptr] <= '9'; ptr++)
        t=t*10+buf[ptr] - '0';
    if (sgn)t=-t;
    return true;
}
}
using namespace IO;

void dfs(int u,int pre)
{
    dp[u][0]=0;         //不选择这个点的最小点覆盖数
    dp[u][1]=1;         //选择这个点的最小点覆盖数
    for(int i=0;i<vec[u].size();i++)
    {
        int v=vec[u][i];
        if(v==pre) continue;
        dfs(v,u);
        dp[u][1]+=min(dp[v][0],dp[v][1]);
        dp[u][0]+=dp[v][1];
    }
}

int main()
{
    IO::begin();
    rush()
    {
        scan_d(n);
        scan_d(k);
        mst(dp,0);
        for(int i=1; i<=n; i++)
        {
            vec[i].clear();
        }
        for(int i=2; i<=n; i++)
        {
            int x;
            scan_d(x);
            vec[x].push_back(i);
            vec[i].push_back(x);
        }
        dfs(1,-1);
        int ans=min(dp[1][0],dp[1][1]);
        if(ans*2>=k)
        {
            printf("%d\n",(k+1)/2);
        }
        else
        {
            ans=ans+k-ans*2;
            printf("%d\n",ans);
        }
    }
    return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值