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;
}
原文地址:https://www.cnblogs.com/Apocrypha/p/10445863.html