[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;
}

本題結。

發佈了152 篇原創文章 · 獲贊 136 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章