题意
给出n个点的一个图,要求把所有点分成两部分,一部分是一个团,一部分是一个独立集.n<=5000
分析
记录自己的愚蠢...
首先考虑如何判断存在可行方案,那么每个点要么属于团,要么属于独立集,且可以根据一个点属于哪一部分推出其他某些属于哪一部分.(如果我们钦定点A属于团,那么所有和A没有连边的点都必须属于独立集,如果我们钦定点A属于独立集,那么所有和A有连边的点都必须属于团).这样的二元关系相互推导的模型自然可以转化为2-SAT.点数5000,边数5000*5000,所以需要线性的做法,跑tarjan强联通分量.
然后我就不会怎么统计方案了,愚蠢*1
看题解,发现有个很妙的性质:假如我们有两个不同的方案都是合法的,那么不可能存在两个不同的点u,v使得这两个点在一个方案中都在独立集里,在另一个方案中都在团里.正确性是显然的,然而没有想到.这个结论虽然简单却非常有力:只需要找出任意一个可行解,那么其他可行解都可以由这个可行解进行次数不多的修改得到.(有三种情况:把独立集中的一个点拿到团里;把团里的一个点拿到独立集里;把团里的一个点和独立集里的一个点互换),那么我们枚举所有可能的修改情况即可.
然后我就把tarjan求强连通分量写错了.把x出栈后,我把in_stack[x]赋值为true...(应当赋值为false).愚蠢*2
过了样例交一发,然后WA了.愚蠢*3
发现有个if里的判断条件少加了一个!,意思全反了.改过来交,又WA了.愚蠢*4
发现我求出来的方案不一定两个集合都非空.改过来交,又WA了.愚蠢*5
发现我默认2SAT求出来的初始解两个集合都非空.改过来交,终于A了.
这做题状态不行啊...
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int maxn=10005,maxm=25000005;
struct edge{
int to,next;
}lst[maxm],lst2[maxm];int len=1,first[maxn],len2=1,first2[maxn];
void addedge(int a,int b){
lst[len].to=b;lst[len].next=first[a];first[a]=len++;
}
void addedge2(int a,int b){
lst2[len2].to=b;lst2[len2].next=first2[a];first2[a]=len2++;
}
int conv(int a,int t){
return (a<<1)|t;
}//conv(a,1):a is spy conv(a,0):a is supply
int n;
bool E[maxn][maxn];
int dfn[maxn],T,stk[maxn],top,low[maxn],belong[maxn],tot;
bool ins[maxn];
vector<int> scc[maxn];
void dfs(int x){
dfn[x]=low[x]=++T;ins[stk[top++]=x]=true;
for(int pt=first[x];pt;pt=lst[pt].next){
if(!dfn[lst[pt].to]){
dfs(lst[pt].to);
if(low[lst[pt].to]<low[x])low[x]=low[lst[pt].to];
}else if(ins[lst[pt].to]&&dfn[lst[pt].to]<low[x])low[x]=dfn[lst[pt].to];
}
if(dfn[x]==low[x]){
++tot;
do{
ins[stk[--top]]=false;
belong[stk[top]]=tot;
scc[tot].push_back(stk[top]);
}while(stk[top]!=x);
}
}
bool twosat(){
for(int i=2;i<=2*n+1;++i){
if(!dfn[i])dfs(i);
}
for(int i=1;i<=n;++i){
if(belong[conv(i,0)]==belong[conv(i,1)])return false;
}
return true;
}
int col[maxn],scccol[maxn];
bool mark[maxn];
bool toposorted[maxn];
int seq[maxn],cnt;
void toposort(int x){
if(toposorted[x])return;
for(int pt=first2[x];pt;pt=lst2[pt].next){
toposort(lst2[pt].to);
}
seq[++cnt]=x;toposorted[x]=true;
}
void getsol(){
for(int i=2;i<=2*n+1;++i){
for(int pt=first[i];pt;pt=lst[pt].next){
if(belong[i]!=belong[lst[pt].to]){
addedge2(belong[i],belong[lst[pt].to]);
}
}
}
for(int i=1;i<=tot;++i)toposort(i);
for(int i=1;i<=tot;++i){
int x=seq[i];
if(mark[x]&&scccol[x]==0)continue;
mark[x]=true;scccol[x]=1;
for(vector<int>::iterator pt=scc[x].begin();pt!=scc[x].end();++pt){
mark[belong[(*pt)^1]]=true;scccol[belong[(*pt)^1]]=0;
}
}
}
int cnt_anti[maxn],anti[maxn];
int main(){
scanf("%d",&n);
for(int i=1,num,x;i<=n;++i){
scanf("%d",&num);
while(num--){
scanf("%d",&x);E[i][x]=true;
}
}
for(int i=1;i<=n;++i){
for(int j=i+1;j<=n;++j){
if(E[i][j]){
addedge(conv(i,1),conv(j,0));
addedge(conv(j,1),conv(i,0));
}else{
addedge(conv(i,0),conv(j,1));
addedge(conv(j,0),conv(i,1));
}
}
}
if(!twosat())printf("0\n");
else{
getsol();
for(int i=1;i<=n;++i)col[i]=scccol[belong[conv(i,1)]];
//for(int i=1;i<=n;++i)printf("%d",col[i]);
for(int i=1;i<=n;++i){
if(!col[i]){
for(int j=1;j<=n;++j){
if(col[j]&&E[i][j]){
anti[i]=j;cnt_anti[i]++;
}
}
}else{
for(int j=1;j<=n;++j){
if((!col[j])&&(!E[i][j])){
anti[i]=j;cnt_anti[i]++;
}
}
}
}
int num[2]={0,0};
for(int i=1;i<=n;++i)num[col[i]]++;
int ans=(num[0]!=0&&num[1]!=0);
for(int i=1;i<=n;++i){
if(cnt_anti[i]==0&&num[col[i]]!=1)ans++;
}
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
if(col[i]&&(!col[j])){
if(cnt_anti[i]==0||(cnt_anti[i]==1&&anti[i]==j)){
if(cnt_anti[j]==0||(cnt_anti[j]==1&&anti[j]==i))ans++;
}
}
}
}
printf("%d\n",ans);
}
return 0;
}