[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
#include
#include
#include
#include
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;
}

你可能感兴趣的:(贪心,基环树)