[NOIP2017模拟]拆网线

题目描述
企鹅国的网吧们之间由网线互相连接,形成一棵树的结构。现在由于冬天到了,供暖部门缺少燃料,于是他们决定去拆一些网线来做燃料。但是现在有 K 只企鹅要上网和别人联机游戏,所以他们需要把这 K 只企鹅安排到不同的机房(两只企鹅在同一个机房会吵架),然后拆掉一些网线,但是需要保证每只企鹅至少还能通过留下来的网线和至少另一只企鹅联机游戏。

所以他们想知道,最少需要保留多少根网线?

输入格式
第一行一个整数 T ,表示数据组数;
每组数据第一行两个整数 N,K ,表示总共的机房数目和企鹅数目。
第二行 N-1 个整数,第 i 个整数 Ai 表示机房i+1和机房 Ai 有一根网线连接(1≤ Ai ≤i)。

输出格式
每组数据输出一个整数表示最少保留的网线数目。

样例数据
输入

2
4 4
1 2 3
4 3
1 1 1

输出

2
2

备注
【数据范围】
对于 30% 的数据:N≤15;
对于 50% 的数据:N≤300;
对于 70% 的数据:N≤2000;
对于 100% 的数据:2≤K≤N≤100000,T≤10。

分析:这道题很容易想到一根网线就可以搞定两个企鹅,显然是最优的,毕竟如果在这样一个连通块中再加企鹅的话就是一根网线搞定一个企鹅了。所以我们就要在这棵树上找尽量多的两个相连的的块。然后我就不知道怎么找orz,于是贪心乱搞……考完反应过来,按拓扑序排一下来配对就可以了呀!事实证明正解确实是拓扑序,但是我乱贪心竟然也可以得50分(满足)。拓扑序排一下为什么是最优的呢?因为相当于是从叶子节点往上配对,如下图,如果从根节点开始配对,会是这样的:
这里写图片描述
但是从叶节点开始配对就可以是这样的:
这里写图片描述
也就是从下面往上配不影响上面的答案,因为每个儿子只有一个父亲,也就只能和父亲配对,而父亲有多个儿子,不知道和哪一个配对,这两种都可以配一对但是第二种就不知道自己是否影响了儿子和儿子的儿子的配对,而第二种是满足了儿子再看父亲(也可以想成树肯定是越深的地方同一层越多,显然让点更多的地方配对配出来的也更多)。我在考场上想到了应该从叶节点开始配对,叶节点有什么特殊的地方呢?就是只连了一条边,于是我就是按连边多少排序来配对的,而且也想到了深度,也作为第二关键字判断了的,但就是没想到拓扑序(看来是第一、二题考兴奋了)……
所以这次考试总分100+70+50=220,排名第十,感觉还行,毕竟题目简单,但是第二题那30分确实扣得冤,250分的话就第七了,试想如果最后一题再深入思考想到拓扑序,那岂不是AK了(流口水……),所以说“题易人易拼细心,题难人难拼心态”啊。

代码
50%:贪心乱搞

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
#include<queue>
#include<set>
using namespace std;

int getint()
{
    int sum=0,f=1;
    char ch;
    for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());
    if(ch=='-')
    {
        f=-1;
        ch=getchar();
    }
    for(;isdigit(ch);ch=getchar())
        sum=(sum<<3)+(sum<<1)+ch-48;
    return sum*f;
}

const int maxn=100010;
struct node{
    int pos;
    int num;
}p[maxn];
int T,n,k,tot,ans;
int first[maxn],nxt[maxn*2],to[maxn*2];
int num[maxn],dep[maxn];
bool visit[maxn];

void addedge(int x,int y)
{
    tot++;
    nxt[tot]=first[x];
    first[x]=tot;
    to[tot]=y;
    tot++;
    nxt[tot]=first[y];
    first[y]=tot;
    to[tot]=x;
}

bool comp(const node &a,const node &b)
{
    return a.num<b.num;
}

void dfs(int u)
{
    for(int p=first[u];p;p=nxt[p])
    {
        int v=to[p];
        if(!visit[v])
        {
            visit[v]=true;
            dep[v]=dep[u]+1;
            dfs(v);
        }
    }
}

int main()
{
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);

    T=getint();
    while(T--)
    {
        memset(first,0,sizeof(first));//清零操作
        memset(visit,0,sizeof(visit));
        memset(num,0,sizeof(num));
        memset(dep,0,sizeof(dep));
        memset(p,0,sizeof(p));
        tot=0,ans=0;
        n=getint(),k=getint();
        for(int i=1;i<=n;++i)
            p[i].pos=i;

        int x;
        for(int i=1;i<n;++i)
        {
            x=getint();
            addedge(i+1,x);
            p[x].num++;
            num[x]++;
            p[i+1].num++;
            num[i+1]++;
        }

        visit[1]=true;
        dep[1]=0;
        dfs(1);//dfs处理一下深度
        memset(visit,0,sizeof(visit));
        sort(p+1,p+n+1,comp);//按连的边多少排序

        for(int i=1;i<=n;++i)//从连的边最少的开始找配对
        {
            if(k<=1)//因为每次搞定两只企鹅,所以如果k<=1了就必须跳掉,不然出负数
                break;
            int u=p[i].pos;
            if(!visit[u])
            {
                int cnt=0x3f3f3f3f,bh=-1;
                for(int p=first[u];p;p=nxt[p])
                {
                    int v=to[p];
                    if(!visit[v])
                        if(num[v]<cnt||(num[v]<=cnt&&dep[v]>dep[u]))//找配对也是找连的边少的或者是更深的
                            bh=v,cnt=num[v];
                }

                if(bh!=-1)
                {
                    visit[u]=true,visit[bh]=true;
                    ans+=1;
                    k-=2;
                }
            }
        }

        ans+=k;//如果剩了几只(企鹅是奇数或者网线两两配对完了还没有搞定所有企鹅),
//就加几根网线变成三人联机(或者多人联机,你怎么想都可以反正是一根网线搞定一只企鹅)
        cout<<ans<<'\n';
    }
    return 0;
}

100%:就是把我贪心的部分改成拓扑序

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
#include<queue>
#include<set>
using namespace std;

int getint()
{
    int sum=0,f=1;
    char ch;
    for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());
    if(ch=='-')
    {
        f=-1;
        ch=getchar();
    }
    for(;isdigit(ch);ch=getchar())
        sum=(sum<<3)+(sum<<1)+ch-48;
    return sum*f;
}

const int maxn=100010;
int T,n,k,tot,cnt,ans;
int first[maxn],nxt[maxn*2],to[maxn*2];
int top[maxn];
bool visit[maxn];

void addedge(int x,int y)
{
    tot++;
    nxt[tot]=first[x];
    first[x]=tot;
    to[tot]=y;
    tot++;
    nxt[tot]=first[y];
    first[y]=tot;
    to[tot]=x;
}

void dfs(int u)
{
    for(int p=first[u];p;p=nxt[p])
    {
        int v=to[p];
        if(!visit[v])
        {
            visit[v]=true;
            dfs(v);
        }
    }

    top[++cnt]=u;
}

int main()
{
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);

    T=getint();
    while(T--)
    {
        memset(first,0,sizeof(first));//清零操作
        memset(visit,0,sizeof(visit));
        memset(top,0,sizeof(top));
        tot=0,cnt=0,ans=0;
        n=getint(),k=getint();

        int x;
        for(int i=1;i<n;++i)
        {
            x=getint();
            addedge(i+1,x);
        }

        visit[1]=true;
        dfs(1);//拓扑排序

        memset(visit,0,sizeof(visit));
        for(int i=1;i<=n;++i)
        {
            if(k<=1)
                break;
            int u=top[i];
            if(!visit[u])//因为是拓扑排序的,所以每个点的子节点都已经是判断过了,也就是说找配对绝对找到的是父亲
            {
                for(int p=first[u];p;p=nxt[p])
                {
                    int v=to[p];
                    if(!visit[v])
                    {
                        visit[u]=true,visit[v]=true;
                        ans+=1;
                        k-=2;
                        break;
                    }
                }
            }
        }

        ans+=k;
        cout<<ans<<'\n';
    }
    return 0;
}

本题结。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值