維護一個保護集合$S$,表示哪些點$A$可能勝利。
首先將所有綠點加入$S$。
$1.$對於一個不在$S$的$A$點,若它存在某個後繼在$S$中,則將其加入$S$。
$2.$對於一個不在$S$的$B$點,若它所有後繼都在$S$中,則將其加入$S$。
通過拓撲可以$O(n+m)$求出$S$集合,那麼剩下的點$A$必敗。
$1.$對於一個在$S$的$A$點,若它所有後繼都不在$S$中,則將其從$S$中移除。
$2.$對於一個在$S$的$B$點,若它存在某個後繼不在$S$中,則將其從$S$中移除。
同樣可以通過拓撲$O(n+m)$求出最終的$S$集合。
這樣會導致某些綠點不在$S$中,那麼它們就失去了作為綠點的價值,將其標記為白點。
重複執行這個演算法$O(n)$輪直到所有綠點都發揮了價值,此時$S$中的點$A$必勝。
時間複雜度$O(n(n+m))$。
#include<cstdio> const int N=3010,M=30010; int n,m,i,j,x,c[N],d[N],g[N],v[M],nxt[M],ed,q[N],vis[N],deg[N],cnt; inline void add(int x,int y){d[v[++ed]=x]++;nxt[ed]=g[y];g[y]=ed;} bool solve(){ int i,x,h=1,t=0; for(i=1;i<=n;i++){ vis[i]=c[i],deg[i]=d[i]; if(vis[i])q[++t]=i; } while(h<=t)for(i=g[q[h++]];i;i=nxt[i])if(!vis[x=v[i]]) if(x<=m)vis[q[++t]=x]=1; else if(!(--deg[x]))vis[q[++t]=x]=1; for(i=h=1,t=0;i<=n;i++){ deg[i]=d[i]; if(!vis[i])q[++t]=i; } while(h<=t)for(i=g[q[h++]];i;i=nxt[i])if(vis[x=v[i]]) if(x>m)vis[q[++t]=x]=0; else if(!(--deg[x]))vis[q[++t]=x]=0; for(t=0,i=1;i<=n;i++)if(c[i]&&!vis[i])c[i]=0,t=1; return t; } int main(){ scanf("%d%d",&m,&n);n+=m; for(i=1;i<=n;i++)for(scanf("%d%d",&c[i],&j);j--;add(i,x))scanf("%d",&x); while(solve()); for(i=1;i<=n;i++)if(vis[i])q[++cnt]=i; for(printf("%d\n",cnt),i=1;i<=cnt;i++)printf("%d\n",q[i]); return 0; }