题意
给定一个
n
n
n 个节点的图,每个点有且仅有一条出边,选取最多的点使得没有边连接相邻两个选中点。
1
≤
n
≤
500000
1 \leq n \leq 500000
1≤n≤500000
思路
把图当无向图看,就是求无向图中的最大独立集。但这张图不是一般的图,它是一棵“基环内向树”,如下图所示:
这种图最明显的特征就是每个点只有一条出边,根据这个性质,每个点按边的指向
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;
}