[BZOJ5303][HAOI2018]反色游戏(Tarjan)
暴力做法是列异或方程组后高斯消元,答案为2^自由元个数,可以得60分。但这个算法已经到此为止了。
从图论的角度考虑这个问题,当原图是一棵树时,可以从叶子开始唯一确定每条边的选择情况,所以答案为1。
于是首先,对一个连通块,若其中黑点个数为奇数则必然无解,否则考虑求出它的一棵生成树。然后当我们选择一条非树边(u,v)时,只需要将树上u,v两点间的所有边的选取情况取反,就又得到一个合法方案。于是答案为$2^{m-n+1}$。进一步可以发现,设原图连通块个数为c,则答案为$2^{m-n+c}$。
现在考虑删去一个点后的改变:
若此点不是割点,则根据它的颜色更新有奇数个黑点的连通块个数,直接输出即可。
若此点是割点,那么还要考虑删去它后,被分割后的几个连通块中是否出现了有奇数个黑点的连通块。
那么,对于每个割点,我们需要计算(其中“之后”是指DFS序在i之后):w[i]这个点及之后的点中黑点的个数,d[i]删去这个点后之后的被分割出的连通块中是否存在有奇数个黑点的,f[i]删去这个点后会新增多少个连通块(对根特判)。最后分情况讨论无解即可。
1 #include<cstdio> 2 #include<algorithm> 3 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 4 #define For(i,x) for (int i=h[x],k; i; i=nxt[i]) 5 using namespace std; 6 7 const int N=100010,mod=1e9+7; 8 char ch; 9 int T,n,m,u,v,s[N],w[N],b[N],dfn[N],low[N],f[N],d[N],ind[N],bin[N]; 10 int cnt,ID,tim,ans,c,h[N],to[N<<1],nxt[N<<1]; 11 12 void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; } 13 void init(){ cnt=tim=c=0; rep(i,1,n) dfn[i]=f[i]=d[i]=ind[i]=h[i]=0; } 14 15 void Tarjan(int x){ 16 b[x]=ID; dfn[x]=low[x]=++tim; 17 For(i,x) if (!dfn[k=to[i]]){ 18 Tarjan(k); low[x]=min(low[x],low[k]); w[x]+=w[k]; 19 if (low[k]>=dfn[x]) d[x]|=w[k]&1,f[x]++; 20 }else low[x]=min(low[x],dfn[k]); 21 if (x==ID) f[x]--; 22 } 23 24 int main(){ 25 freopen("game.in","r",stdin); 26 freopen("game.out","w",stdout); 27 bin[0]=1; rep(i,1,200000) bin[i]=(bin[i-1]<<1)%mod; 28 for (scanf("%d",&T); T--; ){ 29 scanf("%d%d",&n,&m); init(); ans=m-n; 30 rep(i,1,m) scanf("%d%d",&u,&v),add(u,v),add(v,u),ind[u]++,ind[v]++; 31 rep(i,1,n) scanf(" %c",&ch),w[i]=s[i]=ch-'0'; 32 rep(i,1,n) if (!dfn[i]) ID=i,Tarjan(i),ans++,c+=w[i]&1; 33 printf("%d ",c ? 0 : bin[ans]); 34 rep(i,1,n){ 35 if (d[i]) printf("0 "); 36 else if (c-(w[b[i]]&1)) printf("0 "); 37 else if ((w[b[i]]-s[i])&1) printf("0 "); 38 else printf("%d ",bin[ans-ind[i]+1+f[i]]); 39 } 40 puts(""); 41 } 42 return 0; 43 }