BZOJ2948 : [Poi2001]绿色游戏

维护一个保护集合$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;
}

  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值