【洛谷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;
}
posted @ 2021-10-17 10:04  stoorz  阅读(42)  评论(0编辑  收藏  举报