1. 程式人生 > >COCI2014/2015 Contest#1 D MAFIJA(樹形DP/貪心)

COCI2014/2015 Contest#1 D MAFIJA(樹形DP/貪心)

題意

給定一個 n n 個節點的圖,每個點有且僅有一條出邊,選取最多的點使得沒有邊連線相鄰兩個選中點。
1 n 500000

1 \leq n \leq 500000

思路

把圖當無向圖看,就是求無向圖中的最大獨立集。但這張圖不是一般的圖,它是一棵“基環內向樹”,如下圖所示:
基環內向樹示例
這種圖最明顯的特徵就是每個點只有一條出邊,根據這個性質,每個點按邊的指向 dfs \text{dfs} ,總能遇到一個環,基環外向樹則反之。而對於一個連通塊而言,拆去環上的一條邊,形成的無向圖是一棵樹。這些性質為樹形 DP

\text{DP} 和貪心提供了條件。
樹上的最大獨立集就是簡單的“取與不取”的轉移,想要求解必須要轉化成樹的問題。我們可以通過分析答案來決定決策(終態分析),任意拆去環上的一條邊 ( a , b )
(a,b)
最後的答案中 a , b a,b 中至少有一個點不取,那我們斷去 ( a , b ) (a,b) 邊後,以 a a 為根,以 b b 為根分別 dp \text{dp} 一次,分別取 d p a , 0 , d p b , 0 dp_{a,0},dp_{b,0} ,再取 max \max 。最後把所有連通塊的答案相加即可。其中因為只需任意拆一條邊,也只用拆一條邊,可以在並查集並邊的時候,不連線將會成環的邊,並存下這條邊即可。這種寫法在基環樹轉一般樹的寫法中有拓展性。
貪心的寫法正確性不是那麼顯然。首先對於環外的情況,肯定是挑入度為零的點,可以證明假設挑入度更大的點沒有入度為零的點優。那就可以按照這個步驟隔一個的挑點。假設標到了環上的點,那就可以直接把環剖開了,最後把沒剖開的環重新剖一次。只需要用一個 vis \text{vis} 陣列去標記遍歷過的點,然後傳一個引數表示這個點選不選,同時統計答案。

程式碼

樹形DP
#include<bits/stdc++.h>
#define FOR(i,x,y) for(int i=(x),i##END=(y);i<=i##END;++i)
#define DOR(i,x,y) for(int i=(x),i##END=(y);i>=i##END;--i)
typedef long long LL;
using namespace std;
const int N=1e5+3;
template<const int maxn,const int maxm>struct Linked_list
{
    int head[maxn],to[maxm],nxt[maxm],tot;
    void clear(){memset(head,-1,sizeof(head));tot=0;}
    void add(int u,int v){to[++tot]=v,nxt[tot]=head[u],head[u]=tot;}
    #define EOR(i,G,u) for(int i=G.head[u];~i;i=G.nxt[i])
};
Linked_list<N,N<<1>G;
int fa[N],n;
int getfa(int k){return k==fa[k]?k:fa[k]=getfa(fa[k]);}
bool mer(int x,int y)
{
    int fx=getfa(x),fy=getfa(y);
    if(fx==fy)return 0;
    fa[fx]=fy;
    return 1;
}
int To[N],dp[N][2],ans;
int ra[N],rb[N],cnt;
 
void dfs(int u,int f)
{
    dp[u][0]=0,dp[u][1]=1;
    EOR(i,G,u)
    {
        int v=G.to[i];
        if(v==f)continue;
        dfs(v,u);
        dp[u][0]+=max(dp[v][0],dp[v][1]);
        dp[u][1]+=dp[v][0];
    }
}
 
int main()
{
    G.clear();
    int n;
    scanf("%d",&n);
    FOR(i,1,n)fa[i]=i;
    FOR(i,1,n)
    {
        scanf("%d",&To[i]);
        if(mer(i,To[i]))
        {
            G.add(i,To[i]);
            G.add(To[i],i);
        }
        else cnt++,ra[cnt]=i,rb[cnt]=To[i];
    }
    FOR(i,1,cnt)
    {
        dfs(ra[i],0);
        int maxer=dp[ra[i]][0];
        dfs(rb[i],0);
        maxer=max(maxer,dp[rb[i]][0]);
        ans+=maxer;
    }
    printf("%d\n",ans);
    return 0;
}
貪心
#include<bits/stdc++.h>
#define FOR(i,x,y) for(int i=(x),i##END=(y);i<=i##END;++i)
#define DOR(i,x,y) for(int i=(x),i##END=(y);i>=i##END;--i)
typedef long long LL;
using namespace std;
const int N=1e5+3;
int To[N],ind[N],n,ans;
bool vis[N];
 
void dfs(int u,bool kl)
{
    if(vis[u])return;
    vis[u]=1;
    ans+=kl;
    ind[To[u]]--;
    if(!ind[To[u]]||kl)dfs(To[u],!kl);
}
 
int main()
{
    scanf("%d",&n);
    FOR(i,1,n)scanf("%d",&To[i]),ind[To[i]]++;
    FOR(i,1,n)if(!ind[i])dfs(i,1);
    FOR(i,1,n)dfs(i,0);
    printf("%d\n",ans);
    return 0;
}