【BZOJ1854】游戏[SCOI2009](神奇贪心+并查集)

  这道题和今年GDKOI的Day2T2很像(然而gdkoi的题用网络流可以A,这道题只能拿30)。

  网址:http://www.lydsy.com/JudgeOnline/problem.php?id=1854

  题目:

  

   很显然,我们可以立即想到一种解法:如果第i个武器的属性是(a,b),那么就连i->a,i->b两条边,然后就跑网络流。为了确保要从1开始连续攻击,就可以二分答案,用网络流来判断,因为如果能从1打到n,就肯定能从1打到n-1(废话)。判断就是每次只让1~mid的属性值流过去,看一下最后的最大流是否为mid,就知道能不能从1打到mid。

代码:

var f,d,q,x,y:array[0..1000010]of longint;
  a,b,ne:array[0..2000010]of longint;
  n,k,nn,i,j,h,t,p,l,r,m,ans:longint;
procedure add(x,y,z:longint);
begin
  a[p]:=y; b[p]:=z; ne[p]:=f[x]; f[x]:=p; inc(p);
  a[p]:=x; b[p]:=0; ne[p]:=f[y]; f[y]:=p; inc(p);
end;
function dfs(now,ll:longint):longint;
var i,p:longint;
begin
  if now=nn then exit(ll); i:=f[now];
  while i>=0 do begin
    if(b[i]>0)and(d[a[i]]=d[now]+1)then begin
      if ll<b[i] then p:=dfs(a[i],ll)
      else p:=dfs(a[i],b[i]);
      b[i]:=b[i]-p; b[i xor 1]:=b[i xor 1]+p;
      if p>0 then exit(p);
    end;
    i:=ne[i];
  end;
  exit(0);
end;
begin
  read(n);
  for i:=1 to n do read(x[i],y[i]);
  l:=0; r:=10001; nn:=n+10001;
  while l+1<r do begin
    p:=0; m:=(l+r)>>1;
    for i:=0 to nn do f[i]:=-1;
    for i:=1 to n do begin
      add(i,n+x[i],1); add(i,n+y[i],1);
    end;
    for i:=1 to n do add(0,i,1);
    for i:=1 to m do add(n+i,nn,1);
    ans:=0;
    while true do begin
      for i:=0 to nn do d[i]:=0;
      h:=1; t:=1; q[1]:=0; d[0]:=1;
      repeat
        i:=f[q[h]];
        while i>=0 do begin
          if(b[i]>0)and(d[a[i]]=0)then begin
            inc(t); q[t]:=a[i]; d[a[i]]:=d[q[h]]+1;
          end;
          i:=ne[i];
        end;
        inc(h);
      until h>t;
      if d[nn]=0 then break;
      repeat
        p:=dfs(0,1<<25);
        ans:=ans+p;
      until p=0;
    end;
    if m=ans then l:=m else r:=m;
  end;
  writeln(l);
end.
View Code

   然而……这只是30%的解法。

  100%的解法。。。应该怎么说呢。。。并查集+神奇贪心?

  参考黄学长的博客:http://hzwer.com/2950.html

  其实就是把属性为(a,b)的武器看成一条边,然后就生成了一个有10000个结点的无向图,于是我们就要把点和边进行配对(就像上面的二分图匹配一样)。对于其中每一个联通块,有两种情况:

  1、假设它有k个节点,如果有k-1条边,那么肯定有一个节点匹配不到(把它看成一棵有根树,每个节点匹配他的父亲)。为了确保答案最优,那么我们就把这个不能被匹配到的节点定为这个联通块中属性值最大的节点。

  2、如果这个联通块中有>=k条边,这个联通块中就存在环,一定能把全部的点都找到匹配的边(看成环套树,环上的节点一定能被匹配到,而环上的树就按照第一种情况匹配)

  于是就可以用并查集,每次往图中增加一条边时,如果这两个点在同一个联通块,那么这个联通块的根就可以被匹配(这个联通块在加边之后一定成为第二种情况),否则判断是否两个联通块是否有一个存在环(一个有环图无论怎么加还是有环)(我一开始就被这个坑了),否则把编号较小的点修改为可以被匹配,然后把它接到编号较大的点上。这样处理,就可以使每个联通块中最多有一个节点不能被选择,而这个不能被选择的点一定是该联通块中编号最大的节点。最后扫一遍就好了。

代码:

#include<iostream>
#include<cstdio>
using namespace std;
int f[1000010],v[1000010]={0};
int find(int x)
{
    if(f[x]=x)return x;
    int ret=find(f[x]); f[x]=ret;
    return ret;
}
int main()
{
    int n,i,x,y;
    scanf("%d",&n);
    for(i=1;i<=n+1;i++) f[i]=i;
    for(i=1;i<=n;i++){
        scanf("%d%d",&x,&y);
        int fx=find(x),fy=find(y);
        if(fx==fy)v[fx]=1;
        else {
            if(fx>fy){
                int t=fx; fx=fy; fy=t;
            }
            if(v[fx])v[fy]=1;else v[fx]=1;
            f[fx]=fy;
        }
    }
    int ans=1;
    while(v[ans]&&ans<=n)ans++;
    printf("%d",ans-1);
}
View Code

 P.S. 当我做这道题时,ZN大神犇看了一眼后,说:“这不是傻逼贪心吗?” orz orz orz ZN大神犇

原文地址:https://www.cnblogs.com/quzhizhou/p/6441129.html