COCI2014/2015 Contest#1 D MAFIJA(树形DP/贪心)

题意

给定一个 n n n 个节点的图,每个点有且仅有一条出边,选取最多的点使得没有边连接相邻两个选中点。
1 ≤ n ≤ 500000 1 \leq n \leq 500000 1n500000

思路

把图当无向图看,就是求无向图中的最大独立集。但这张图不是一般的图,它是一棵“基环内向树”,如下图所示:
基环内向树示例
这种图最明显的特征就是每个点只有一条出边,根据这个性质,每个点按边的指向 dfs \text{dfs} dfs,总能遇到一个环,基环外向树则反之。而对于一个连通块而言,拆去环上的一条边,形成的无向图是一棵树。这些性质为树形 DP \text{DP} DP 和贪心提供了条件。
树上的最大独立集就是简单的“取与不取”的转移,想要求解必须要转化成树的问题。我们可以通过分析答案来决定决策(终态分析),任意拆去环上的一条边 ( a , b ) (a,b) (a,b) 最后的答案中 a , b a,b a,b 中至少有一个点不取,那我们断去 ( a , b ) (a,b) (a,b) 边后,以 a a a 为根,以 b b b 为根分别 dp \text{dp} dp 一次,分别取 d p a , 0 , d p b , 0 dp_{a,0},dp_{b,0} dpa,0,dpb,0,再取 max ⁡ \max max。最后把所有连通块的答案相加即可。其中因为只需任意拆一条边,也只用拆一条边,可以在并查集并边的时候,不连接将会成环的边,并存下这条边即可。这种写法在基环树转一般树的写法中有拓展性。
贪心的写法正确性不是那么显然。首先对于环外的情况,肯定是挑入度为零的点,可以证明假设挑入度更大的点没有入度为零的点优。那就可以按照这个步骤隔一个的挑点。假设标到了环上的点,那就可以直接把环剖开了,最后把没剖开的环重新剖一次。只需要用一个 vis \text{vis} 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值