zoukankan      html  css  js  c++  java
  • POI2011 同谋者 Conspiracy

    POI2011 同谋者 Conspiracy

    题目传送门

    题意

    (Bitotia)偷袭了(Byteotia)并占领了很大一块领地。(Byteotia)国王(Byteasar)正在被占领的领地内组织人们抵抗运动。国王需要选择一部分人来作为这场运动的核心。他们会被分成两部分:一部分人作为同谋者在领地内指挥,另一部分人作为后勤提供援助。这两部分人需要满足以下条件:

    • 后勤组必须两两认识,确保整个组有效率地合作;
    • 同谋者必须两两不认识;
    • 至少有一个人作为后勤,一个人作为同谋者。

    国王希望知道有多少种方案将人们分成两组,以及是否有可能这样分组。

    题解

    简单题意就是将一个图划分成一个团和一个独立集的方案数。直接计算似乎不可取,我们考虑当前已知一种方案,通过调整两个集合中的人转移到另一种方案。我们记团的集合为(S),独立集的集合为(T),考虑实际上只有三种转移的方式:

    • (S)中的一个人被分到(T)
    • (T)中的一个人被分到(S)
    • (S)中的一个人和(T)中的一个人交换

    并且容易证明不可能同时有两个人从一个集合分到另一个集合中去。所以我们只要搜出一种答案,那么其余的答案都可以通过这种调整方法得到。搜一个答案是比较简单的,用2-set暴力建图跑(Tarjan)就可以了。然后我们考虑方案数的计算,我们记矛盾值(c_i)表示对于一个点,如果被分到另一个集合中,会有多少个点与之冲突。冲突定义为两点在同在(T)中且有边相连,或同在(S)中且无边相连。如果矛盾值大于(0),那么我们记(v_i)表示(i)这个点的任意一个矛盾点。
    那么对于第一、二中调整方案,显然只有一个集合中的点数大于1且被分配到另一个集合的点矛盾值为0。对于第三种调整方案,我们记需要交换的点为(a,b(u in S,v in T)),那么当(a,b)的矛盾值都为(0)时,显然可以直接交换,当(a,b)某一个点的矛盾值为(1)时(假设为(a)),那么(v_a)则必须为(b)时,两者才可能交换,此时,如果(c_b=0),或者(c_b=1)并且(v_b=a)时,两者可以交换。容易发现当(c_a>1)或者(c_b>1)时,两者不能交换。然后暴力计算即可,注意不要重复算贡献。

    Code

    #include<bits/stdc++.h>
    using namespace std;
    const int N=10005;
    int n,tot,clck,tp,bl,c1,c2;
    int lk[N],head[N],low[N],dfn[N],ins[N],st[N],block[N],c[N],is[N];
    bool vis[N/2][N/2],Mp[N/2][N/2];
    vector<int>T,D;
    struct edge {
      int to,nxt;
    }E[25000050];
    
    void Addedge(int u,int v) {
      E[++tot].to=v;E[tot].nxt=head[u];head[u]=tot;
    }
    
    void Tarjan(int o,int fa) {
      dfn[o]=low[o]=++clck;ins[o]=1;
      st[++tp]=o;
      for(int i=head[o];~i;i=E[i].nxt) {
        int to=E[i].to;
        if(!dfn[to]) {
          Tarjan(to,o);
          low[o]=min(low[o],low[to]);
        }
        else if(ins[to]) low[o]=min(low[o],dfn[to]);
      }
      if(dfn[o]==low[o]) {
        ++bl;
        while(tp) {
          int x=st[tp];--tp;
          ins[x]=0;
          block[x]=bl;
          if(x==o) break;
        }
      }
    }
    
    int main() {
      memset(head,-1,sizeof head);
      scanf("%d",&n);
      for(int i=1,k;i<=n;i++) {
        scanf("%d",&k);
        for(int j=1,x;j<=k;j++) {
          scanf("%d",&x);
          lk[x]=1;
          Mp[i][x]=Mp[x][i]=1;
        }
        for(int j=1;j<=n;j++) {
          if(i==j) continue;
          if(lk[j]) {
    	    Addedge(i+n,j);
          }
          else {
    	    Addedge(i,j+n);
          }
          lk[j]=0;
        }
      }
      for(int i=1;i<=n*2;i++) {
        if(!dfn[i]) Tarjan(i,-1);
      }
      for(int i=1;i<=n;i++) {
        if(block[i]==block[i+n]) return puts("0"),0;
        if(block[i]<block[i+n]) T.push_back(i);
        else D.push_back(i);
      }
      for(int i=0;i<(int)T.size();i++) {
        for(int j=0;j<(int)D.size();j++) {
          if(Mp[T[i]][D[j]]) {
    	    c[T[i]]++;
    	    if(!is[T[i]]) is[T[i]]=D[j];
          }
          else {
    	    c[D[j]]++;
    	    if(!is[D[j]]) is[D[j]]=T[i];
          }
        }
      }
      for(int i=0;i<(int)T.size();i++) if(!c[T[i]]) c1++;
      for(int i=0;i<(int)D.size();i++) if(!c[D[i]]) c2++;
      int ans=(T.size()>=1&&D.size()>=1);
      ans+=c1*c2;
      for(int i=0;i<(int)T.size();i++) {
        if(c[T[i]]!=1) continue;
        int Ano=is[T[i]];
        if(vis[T[i]][Ano]) continue;
        if(!c[Ano]) {
          vis[T[i]][Ano]=vis[Ano][T[i]];
          ans++;
        }
        else if(c[Ano]==1&&is[Ano]==T[i]) {
          vis[T[i]][Ano]=vis[Ano][T[i]];
          ans++;
        }
      }
      for(int i=0;i<(int)D.size();i++) {
        if(c[D[i]]!=1) continue;
        int Ano=is[D[i]];
        if(vis[D[i]][Ano]) continue;
        if(!c[Ano]) {
          vis[D[i]][Ano]=vis[Ano][D[i]];
          ans++;
        }
        else if(c[Ano]==1&&is[Ano]==D[i]) {
          vis[D[i]][Ano]=vis[Ano][D[i]];
          ans++;
        }
      }
      if(T.size()>1) {
        for(int i=0;i<(int)T.size();i++) {
          if(!c[T[i]]) ans++;
        }
      }
      if(D.size()>1) {
        for(int i=0;i<(int)D.size();i++) {
          if(!c[D[i]]) ans++;
        }
      }
      printf("%d
    ",ans);
      return 0;
    }
    
  • 相关阅读:
    CCF NOI1079 合法C标识符
    CCF NOI1080 统计字符
    CCF NOI1076 进制转换
    CCF NOI1065 最小公倍数
    CCF NOI1139 高精度减法
    CCF NOI1138 高精度加法
    CCF NOI1115 找数
    CCF NOI1097 数列
    CCF NOI1089 高精度运算
    NUC1931 Problem D 区间素数【素数筛选】
  • 原文地址:https://www.cnblogs.com/Apocrypha/p/10445863.html
Copyright © 2011-2022 走看看