展翅翱翔之时 (はばたきのとき)

容易看出考察基环树(或者说“环套树”)的熟练运用。

变量解释:

变量名称 解释
\(c_i\) \(i\) 个卫星调整接收源的所需花费
\(in_i\) 指向点 \(i\) 的边的编号
\(out_i\) 从点 \(i\) 延出的边的编号
\(vis_i\) \(vis_i = 0\) 时未访问;\(vis_i = 1\) 时已访问但没在环上;\(vis_i = 2\) 时为在环上的点
\(fa_i\) 原本指向点 \(i\) 的点的编号
\(loop_i\) 环上的点

题目大意

给出一个有向图,求把这个有向图变成一个环的最小代价。

思路分析

对于一个基环树森林,我们将其分为几个独立的基环树,每一棵基环树把权值最大的取除,所有操作全部完成之后,再把已经不完整的基环树们连成一个环即可。

特别地,找到一棵基环树之后,如果一棵基环树的环的点数正好有 \(N\) 个,那么它已经满足条件是一棵基环树了,直接输出 \(0\) 即可,否则 #\(4\) 和 #\(7\) 会出错。


整个程序分为主函数、深度优先遍历和找环。

找环

选定一个需要深入的点 \(u\)

  1. 如果点 \(u\) 未被访问,则显示访问此节点,并且依次访问指向此节点的点。

  2. 如果点 \(u\) 已被访问,那么将其放入环中,并且标记为环中的节点,并且依次访问指向此节点的点。

这是一个找环的过程,目的是找出环为接下来的 dfs 做准备。

dfs

对于一个节点 \(u\),访问其出边所连接的点,对其进行遍历。

这个步骤是为了找出价值最大的那条边,然后割掉它。

inline void dfs(int u,int fath)
{
	if(vis[u]==0)
	{
		vis[u]=1;
	}
	
	for(register int i=head[u];i;i=node[i].nxt)
	{
		int v=node[i].v;
		
		if(v!=fath && vis[v]!=2)
		{
			dfs(v,u);
			
			if(c[out[u]]>c[i])
			{
				sum+=c[i];
			} 
			
			else
			{
				sum+=c[out[u]];
				
				out[u]=i;
			}
		}
	}
}

完整代码:

//2021/8/4

#include <cstdio>

#define int long long

namespace sol
{
	inline int min(int x,int y)
	{
		return x<y?x:y;
	}
}

using namespace sol;

using namespace std;

const int INF=(1ll<<50);

const int ma=100005;

struct Node
{
	int v;
	
	int nxt;
};

Node node[ma<<1];

int head[ma],in[ma],out[ma],vis[ma],fa[ma],loop[ma],c[ma]; 

int n;

int idx,sum;

inline void add(int u,int v,int w)
{
	node[++idx].v=v;
	
	c[idx]=w;
	
	node[idx].nxt=head[u];
	
	head[u]=idx;
}

inline void dfs(int u,int fath)
{
	if(vis[u]==0)
	{
		vis[u]=1;
	}
	
	for(register int i=head[u];i;i=node[i].nxt)
	{
		int v=node[i].v;
		
		if(v!=fath && vis[v]!=2)
		{
			dfs(v,u);
			
			if(c[out[u]]>c[i])
			{
				sum+=c[i];
			} 
			
			else
			{
				sum+=c[out[u]];
				
				out[u]=i;
			}
		}
	}
}

inline void getloop(int u)
{
	idx=0;
	
	while(vis[u]==0)
	{
		vis[u]=1;
		
		u=fa[u];
	}
	
	while(vis[u]==1)
	{
		loop[++idx]=u;
		
		vis[u]=2;
		
		u=fa[u];
	}
}

#undef int

int main(void)
{
	#define int long long
	
	scanf("%lld",&n);
	
	for(register int i=1;i<=n;i++)
	{
		int val;
		
		scanf("%lld%lld",&fa[i],&val);
		
		in[i]=i;
		
		add(fa[i],i,val);
	}
	
	for(register int i=1;i<=n;i++)
	{
		if(vis[i]==0)
		{
			getloop(i);
			
			if(idx==n)
			{
				printf("0\n");
				
				return 0;
			}
			
			int ans1=0,ans2=INF;
			
			for(register int j=1;j<=idx;j++)
			{
				dfs(loop[j],0);
			}
			
			for(register int j=1;j<=idx;j++)
			{
				ans2=min(min(ans1,ans2)+c[in[loop[j]]],ans2+c[out[fa[loop[j]]]]); 
				
				ans1+=c[out[fa[loop[j]]]];
			} 
			
			sum+=ans2;
		}
	}
	
	printf("%lld\n",sum);
	
	return 0;
}
posted @ 2021-08-04 15:11  Coros_Trusds  阅读(82)  评论(0编辑  收藏  举报