jzoj. 3929. 【NOIP2014模拟11.6】创世纪

Description

上帝手中有着n种被称作“世界元素”的东西,现在他要把它们中的一部分投放到一个新的空间中去以建造世界。每种世界元素都可以限制另外一种世界元素,所以说上帝希望所有被投放的世界元素都有至少一个没有被投放的世界元素能够限制它,这样上帝就可以保持对世界的控制。
由于那个著名的有关于上帝能不能制造一块连自己都不能举起的大石头的二律背反命题,我们知道上帝不是万能的,而且不但不是万能的,他甚至有事情需要找你帮忙——上帝希望知道他最多可以投放多少种世界元素,但是他只会O(2^n)级别的算法。虽然上帝拥有无限多的时间,但是他也是个急性子。你需要帮助上帝解决这个问题。

Input

第一行一个正整数n,表示世界元素的数目。
第二行n个正整数a_1, a_2, …, a_n。a_i表示第i个世界元素能够限制的世界元素的编号。

Output

最多可以投放的世界元素的数目。

Sample Input

6
2 3 1 3 6 5

Sample Output

3
样例说明:
选择2、3、5 三个世界元素即可,分别有1、4、6来限制它们。

Data Constraint

30%的数据,n<=10。
60%的数据,n<=10^5。
100%的数据,a_i<=n<=10^6。

【解法一】按照题目给出的O( 2^n ) 的上帝算法,枚举每个点是否在集合内判断即可
期望得分 30 分

【模型转换】我们把“限制”的关系转化成一条边,即若y会被x限制,则连一条从x到y的有向边。那么原图就会变成了若干个联通块,每个联通块必定由一个环以及若干棵“挂”在环上的树组成,即环套树。

【解法二】贪心。找出所有没有入度的点,那么这些点必定不能选入集合中,我们沿着这些点上溯,将可以选进集合的点都选进集合。这里“可以选进”的定义为存在一个没有被选进集合的前驱。于是环上就会有若干个由树上决定它要选进集合的点。我们将环按照已选入集合的点来分裂成若干段,每一段按照原来的贪心方法来贪心即可。
具体证明将在后面给出。
时间复杂度 O( n )
期望得分 100 分

【解法三】Dp。实际上我们选取一个点进集合我们亦选取了某个点使得它不在集合内。于是我们实际上是选取了若干条不相交的边,即最大边独立集。在这里不相交的定义为没有公共的顶点。这个问题在环套树的情况下(一般图没法做?)是可以用Dp解决的。设F[i]表示以i为根的最大边独立集的大小,i不被选,G[i]表示i被选。那么
G[i] = sigma( max( G[j], F[j] ) ), ( j, i )∈E。即所有点i的前驱的G值和F值较大者之和。
F[i] = G[i] + max( G[j] – max( G[j], F[j] ) ), ( j, i )∈E。即存在一个前驱不选,其余随意。
这样我们就可以算出以环上每个点为根的“挂在环上的”子树的最大可选边数。对于环上,由于第一个点和最后一个点会相互影响,我们可以枚举最后一个点的三种状态:不选;选且树上已有点限制它;选但树上没有点限制,需要环上的点限制它。然后就可以Dp取最大值即可。
时间复杂度 O( n )
期望得分 100 分

【贪心正确性的证明】
由解法三我们发现选出的实际上是若干条不相交的边。而解法二选出的一系列点集显然是符合要求的。我们只需判断是否有可以选出更多的点即可。我们显然不能将未选入的点选入(能选入的我们都选了),但可以将已选入的点剔除,试图将一些未选入的点选入来达到扩充的要求。下面将证明这样不会使得选入的点数增加。
我们将一个点从“选”的状态变成不选,显然不会改变其前驱的状态(前驱并不依赖于这个点),那么我们考虑一下后继的情况。
如果这个点x的唯一的后继y并没有被选入,那么说明它原来所有的前驱都被选入了。我们可以将它选入点集中。但是这样子可能导致y的唯一后继z无法被选入了(这种情况就是z被选入点集时依赖于y),从下图可以更直观地看出这种关系。

这里写图片描述
那么这时候我们就可以有两种选择,直接舍弃绿叉边,又或者将它继续往下“推”,那么递归地来看这和我们做的第一步是相同的,显然这么做并不能使得被选入边集的边数增多。
如果这个点x的唯一的后继y被选入了,那么情况是类似的,不难证明不会使得边数增多。
综上所述,通过上面所述的贪心策略能选出最大的点集。

(by jzoj)
(上面内容为转载,若有疑惑,概不负责)

代码:

var
 n,i,j,h,t,u,x,ans,sum:longint;
 a,r,g:array [1..1000001] of longint;
 list:array [1..1000001] of longint;
 v,b:array [1..1000001] of boolean;
 flag:boolean;

begin
 readln(n);
 for i:=1 to n do
  begin
   read(a[i]);
   inc(r[a[i]]);
  end;
 h:=0; t:=0;
 for i:=1 to n do
  if r[i]=0 then
   begin
    inc(t);
    list[t]:=i;
   end;
 repeat
  inc(h);
  u:=list[h];
  if (v[u]=false) and (v[a[u]]=false) then
   begin
    ans:=ans+1;
    v[a[u]]:=true;
    dec(r[a[a[u]]]);
    if r[a[a[u]]]=0 then
     begin
      inc(t);
      list[t]:=a[a[u]];
     end;
   end;
  v[u]:=true;
 until h=t;
 for i:=1 to n do
  begin
   if v[i]=false then
    begin
     sum:=0; j:=i;
     if a[j]<>i then
      begin
       v[j]:=true;
       sum:=sum+1;
       j:=a[j];
      end;
     v[j]:=true;
     ans:=ans+(sum+1) div 2;
    end;
  end;
 writeln(ans);
end.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值