[BZOJ3037/CH6401]创世纪(贪心)

传送门


首先分析题目所给的树结构,将x与x控制的点a[x]连一条有向边,原图就变成了一个内向树森林。
这题放在了基环树的tag下,然后正解是一个树形dp,但是我思考了一下,我发现可以从内向树的最外圈一层一层往里面推进,但是转移的时候根本不需要dp,直接贪心就可以了。

对于一个入度为0的点x,由于x无法被控制,所以只能不选。那么选择x控制的节点a[x]投放一定是最优的。 那么在选择a[x]之后,a[a[x]]就不能被a[x]限制了,那么把他的度数-1,如果a[a[x]]的度数=0,那么说明他也可以去限制别人了,就把他加入待转移集合中。我们可以用一个队列来实现这个操作。

但是以上的转移完成之后,以上的贪心方法类似于拓扑,所以不适用与环,那么环上的点是不会被加入集合的!但是我们发现对于一个长度为cnt的环,可以选择投放的点为cnt/2个(隔一个选一个嘛),那么我们就可以求出每一颗内向树环的长度即可。


#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long ll;
const int N=1e6+10;
inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch<'0' || ch>'9'){if(ch=='-')f=-1; ch=getchar();}
	while(ch>='0' && ch<='9'){x=x*10+ch-'0'; ch=getchar();}
	return x*f;	
}
int a[N],du[N],list[N],head,tail;
bool v[N];

int main()
{
	int n=read();
	for(int i=1;i<=n;i++)
	{
		a[i]=read();
		du[a[i]]++;
	}
	head=1; tail=0;
	for(int i=1;i<=n;i++)
		if(du[i]==0)
			list[++tail]=i;
	int ans=0;
	while(head<=tail)
	{
		int x=list[head];
		if(!v[x] && !v[a[x]])
		{
			ans++; 	
			v[a[x]]=1; 
			du[a[a[x]]]--; if(du[a[a[x]]]==0) list[++tail]=a[a[x]];
		}
		v[list[head]]=1; head++;
	}
	int cnt=0,j;
	for(int i=1;i<=n;i++)
	{
		if(!v[i])
		{
			cnt=0;
			j=i;
			while(a[j]!=i)
			{
				v[j]=1;
				cnt++;
				j=a[j];	
			}
			v[j]=1;
			ans+=(cnt+1)/2;
		}
	}
	printf("%d\n",ans);
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值