1. 程式人生 > >問題 L: 開會--樹的最小支配集

問題 L: 開會--樹的最小支配集

問題 L: 開會

時間限制: 1 Sec  記憶體限制: 128 MB
提交: 111  解決: 20
[提交] [狀態] [討論版] [命題人:admin]

題目描述

開會,是對所有人時間的浪費,是對集體的謀殺。
山區學校的一些學生之間的關係似乎好得有點過頭,以至於傳出了一些(在風紀委員們看來)不好的緋聞。具體地,有n個學生,n-1條緋聞,每條緋聞的主角都是倆學生。記者們的惡趣味保證任意兩個學生,可以通過若干條緋聞直接或間接地聯絡在一起。
於是學校打算邀請一些學生參加座談會。
校長相信,假如邀請了某位學生x來開會,那麼就能夠震懾到x本人,以及和x在同一條緋聞裡的學生們。
礦泉水是寶貴的,校長想知道最少需要請多少人來開會,才有可能震懾到所有同學。

 

輸入

第一行是 n 表示學生數。 
之後n-1行,每行倆整數x,y,表示學生x和y之間有緋聞。( x≠y,但不一定x<y )

輸出

一行,一個整數表示最少要邀請多少人。 

樣例輸入

5
1 3
5 2
4 3
3 5

樣例輸出

2

提示

可以選擇邀請學生2&3,或者是邀請學生3&5 

對於前10%的資料,n<=15
對於前30%的資料,n<=2000
對於接下來30%的資料,n<=10^5,且有倆學生需要通過n-1條緋聞才能扯上關係。
對於前100%的資料,n<=10^5,1<=x,y<=n

 

居然是板子題,嗯,板子還是很好理解的,可惜當時不知道

 

無向圖,兩倍的邊,所以邊開兩倍

 

一遍dfs,

求出某個點的父親,並且將dfs的順序放入棧中(需要反序)

那麼依次彈出棧中點

如果他沒被覆蓋(震懾),那麼標記他父親(這個就是被拉去開會的那個),這樣覆蓋(震懾)掉他,他父親,他父親的父親

棧空即結束

 

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+7;
struct Edge{
    int to,next;
}E[maxn<<1];//邊
int Fa[maxn],head[maxn],st[maxn],now,cnt,n;//Fa-father,head-鏈式前向星,st-棧,now-dfs序,cnt-鏈式前向星邊數,n-樹節點數
bool vis[maxn],Set[maxn];//vis是-是否震懾某人,Set是-某人去開會
int ans;//答案
void dfs(int x,int dad){
    Fa[x] = dad;
    st[now++] = x;//入棧
    for(int i=head[x];~i;i = E[i].next){
        int v = E[i].to;
        if(v == dad)continue;
        dfs(v,x);
    }
}
void add(int u,int v){
    E[cnt] = {v,head[u]};
    head[u] = cnt++;
    E[cnt] = {u,head[v]};
    head[v] = cnt++;
}
void init(){
    memset(head,-1,sizeof(head));
    memset(Fa,0,sizeof(Fa));
    memset(st,0,sizeof(st));
}
int main(){
    init();
    scanf("%d",&n);
    for(int i=0;i<n-1;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        add(x,y);
    }
    dfs(1,0);
    while(now){//棧不空
        int top = st[--now];//取棧頂(這裡理解為倒序遍歷陣列也行)
        if(!vis[top]){
            if(!Set[Fa[top]]){//選其父親去開會
                Set[Fa[top]] = 1;
                ans ++;
            }
            vis[top] = 1;//它
            vis[Fa[top]] = 1;//它父親
            vis[Fa[Fa[top]]] = 1;//它爺爺
        }
    }
    printf("%d\n",ans);
    return 0;
}