P5008 [yLOI2018] 锦鲤抄

发布时间 2023-08-22 19:33:51作者: One_JuRuo

思路

我们可以先找出所有的可以被删除的点,然后取点权前 \(k\) 大的点就好了。

因为图可能存在环,所以我们需要先缩点,考虑缩点后的 DAG,我们可以按照拓扑序倒序删点就好。

再来考虑每个 SCC 如何取点。

我们先把 SCC 分为三种情况:

  1. 该 SCC 没有入度,且无自环。
  2. 该 SCC 没有入度,且有点存在自环。
  3. 该 SCC 有入度。

对于第一种情况,我们只能随意删一个点,然后按照顺序删点,最后会发现最开始删掉的点链接的那个点无法被删除,所以这种情况至少会剩下一个点不能删除,我们可以直接贪心,让权值最小的那个点不删除就好了。

如果,点上的数字代表删除的顺序。

对于第二种情况,我们可以先删除链接有自环点的点,然后按照顺序删点,最后剩下的点因为有自环,可以删除,所以这种情况所有的点都可以被删除。

对于第三种情况,我们可以最后删除有入度来自 SCC 外的点,即第一个删除链接该点的点,所以这种情况所有的点都可以被删除。

所以我们的思路现在很顺畅了:建图-> tarjan 缩点->统计入度->找到可删除的点->取权值前 \(k\) 大的点,计算答案。

AC 代码

#include<bits/stdc++.h>
using namespace std;
long long n,m,k,v[500010],x,y,e[2000010],ne[2000010],h[500010],idx=1,a,dfn[500010],low[500010],cnt,id[500010],z[500010],top,in_z[500010],in[500010];
long long ans[500010],ta,res;
vector<long long>vec[500010];//记录每个SCC有哪些点
inline void add(long long a,long long b){e[idx]=b,ne[idx]=h[a],h[a]=idx++;}//链式前向星
inline bool cmp(long long a,long long b){return v[a]>v[b];}
void dfs(long long u)//tanjar缩点
{
	dfn[u]=low[u]=++cnt,z[++top]=u,in_z[u]=1;
	for(long long i=h[u];i;i=ne[i])
	{
		if(!dfn[e[i]]) dfs(e[i]),low[u]=min(low[u],low[e[i]]);
		else if(in_z[e[i]]) low[u]=min(low[u],dfn[e[i]]);
	}
	if(dfn[u]==low[u])
	{
		long long y;
		do{y=z[top--],vec[u].push_back(y),in_z[y]=0,id[y]=u;}while(y!=u);//用点的编号当SCC的编号
	}
}
int main()
{
	scanf("%lld%lld%lld",&n,&m,&k);
	for(long long i=1;i<=n;++i) scanf("%lld",&v[i]);
	for(long long i=1;i<=m;++i) scanf("%lld%lld",&x,&y),add(x,y);
	for(long long i=1;i<=n;++i) if(!dfn[i]) dfs(i);
	for(long long i=1;i<=n;++i) for(long long j=h[i];j;j=ne[j]) if(i==e[j]||id[i]!=id[e[j]]) in[id[e[j]]]++;//统计入度,存在自环这种特殊情况,但是自环和有入度的情况结果一样,所以把自环也看作入度
	for(long long i=1;i<=n;++i)
		if(id[i]==i)
		{
			if(in[i]){for(long long j=0;j<vec[i].size();++j) ans[++ta]=vec[i][j];}//有入度可以把所有点删完,所以全部加入统计
			else{
				sort(vec[i].begin(),vec[i].end(),cmp);//无入度至少剩一个,贪心剩下权值最小的那个,所以需要排序
				for(long long j=0;j<vec[i].size()-1;++j) ans[++ta]=vec[i][j];
			}
		}
	sort(ans+1,ans+ta+1,cmp);//把所有可删除的点排序
	for(long long i=1;i<=k;++i) res+=v[ans[i]];//取前k个
	printf("%lld",res);
	return 0;
}