【洛谷P5008】锦鲤抄
题目
题目链接:https://www.luogu.com.cn/problem/P5008
给你一张有向图,每个点有一个点权。任意时刻你可以任意选择一个有入度的点,获得它的点权并把它和它的出边从图上删去。最多能选择 \(k\) 个点,求最多能获得多少点权。
\(n\leq 5\times 10^5+4\),\(m\leq 2\times 10^6+4\)。
思路
首先如果给定的图是一张 DAG,那么除了入度为 \(0\) 的点,其他点都按照拓扑序取,一定是可以取任意点的。所以只需要把入度不为 \(0\) 的点的点权排一下序就好了。
对于任意一张有向图,先缩点,因为一个强连通分量一定可以选择其中任意一个点作为根,构造出一棵外向树,而外向树是属于 DAG 的,所以考虑每一个强连通分量:
- 如果这个强连通分量有入度,那么把有入度的点看作是根,这个强连通分量里的点都可以取(其他点按照拓扑序,根最后取)。
- 如果这个强连通分量没有入度,将点权最小的点作为根,这样其他点都可以去,显然这样是最优的。
然后排个序取前 \(k\) 大就行了。
时间复杂度 \(O(m+n\log n)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=500010,M=2000010;
int n,m,k,tot,cnt,ans,a[N],dfn[N],low[N],head[N],bel[N],U[M],V[M];
bool vis[N];
vector<int> scc[N];
stack<int> st;
int read()
{
int d=0; char ch=getchar();
while (!isdigit(ch)) ch=getchar();
while (isdigit(ch)) d=(d<<3)+(d<<1)+ch-48,ch=getchar();
return d;
}
struct edge
{
int next,to;
}e[M];
void add(int from,int to)
{
e[++tot]=(edge){head[from],to};
head[from]=tot;
}
void tarjan(int x)
{
dfn[x]=low[x]=++tot;
st.push(x); vis[x]=1;
for (int i=head[x];~i;i=e[i].next)
{
int v=e[i].to;
if (!dfn[v])
tarjan(v),low[x]=min(low[x],low[v]);
else if (vis[v])
low[x]=min(low[x],dfn[v]);
}
if (dfn[x]==low[x])
{
cnt++;
while (st.top()!=x)
{
int y=st.top(); st.pop();
scc[cnt].push_back(a[y]);
vis[y]=0; bel[y]=cnt;
}
st.pop();
scc[cnt].push_back(a[x]);
vis[x]=0; bel[x]=cnt;
}
}
int main()
{
memset(head,-1,sizeof(head));
n=read(); m=read(); k=read();
for (int i=1;i<=n;i++) a[i]=read();
for (int i=1;i<=m;i++)
{
U[i]=read(); V[i]=read();
add(U[i],V[i]);
}
tot=0;
for (int i=1;i<=n;i++)
if (!dfn[i]) tarjan(i);
for (int i=1;i<=m;i++)
if (bel[U[i]]!=bel[V[i]]) vis[bel[V[i]]]=1;
tot=0;
for (int i=1;i<=cnt;i++)
{
sort(scc[i].begin(),scc[i].end());
for (int j=(!vis[i]);j<(int)scc[i].size();j++)
a[++tot]=scc[i][j];
}
sort(a+1,a+1+tot);
for (int i=tot;i>max(tot-k,0);i--) ans+=a[i];
cout<<ans;
return 0;
}