【loj#2524】【bzoj5303】 [Haoi2018]反色游戏(圆方树)

  题目传送门:loj bzoj

  题意中的游戏方案可以转化为一个异或方程组的解,将边作为变量,点作为方程,因此若方程有解,方程的解的方案数就是2的自由元个数次方。我们观察一下方程,就可以发现自由元数量=边数-点数+连通块数,或者换句话说,若对原图的每个联通块指定一棵生成树,那么确定了生成树之外的边是否进行操作,那么生成树内的边的操作方案就是一定存在并唯一确定的。

  那么我们就只需要判断一下什么样的图无解。我们发现每对一条边进行操作,原图内的黑点数量奇偶性不变,那么我们只需判断图中的是否存在某个联通块有奇数个黑点,若存在即无解。

  加上了删点操作后,我们可以用圆方树来维护连通块信息。因为圆方树的连通性与原图上的连通性相互对应,删除单个点之后,原图被新分成的连通块就是圆方树删除对应点的连通块,那么使用圆方树就可以快速维护删除单个点的连通块信息。

  代码:

#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#define ll long long
#define inf 0x3f3f3f3f
#define mod 1000000007
#define maxn 200010
inline ll read()
{
    ll x=0; char c=getchar(),f=1;
    for(;c<'0'||'9'<c;c=getchar())if(c=='-')f=-1;
    for(;'0'<=c&&c<='9';c=getchar())x=x*10+c-'0';
    return x*f;
}
inline void write(ll x)
{
    static int buf[20],len; len=0;
    if(x<0)x=-x,putchar('-');
    for(;x;x/=10)buf[len++]=x%10;
    if(!len)putchar('0');
    else while(len)putchar(buf[--len]+'0');
}
inline void writeln(ll x){write(x); putchar('\n');}
inline void writesp(ll x){write(x); putchar(' ');}
struct edge{
    int to,nxt;
};
struct Graph{
    edge e[4*maxn];
    int fir[2*maxn],deg[2*maxn];
    int tot;
    inline void clear()
    {
        memset(fir,255,sizeof(fir)); tot=0;
        memset(deg,0,sizeof(deg));
    }
    inline void add_edge(int x,int y)
    {
        e[tot].to=y; e[tot].nxt=fir[x]; fir[x]=tot++;
        ++deg[x];
    }
}G,T;
int dfn[maxn],low[maxn],st[maxn],ans[maxn];
int val[2*maxn],size[2*maxn],fa[2*maxn],rt[2*maxn];
char s[maxn];
int n,m,tot,tp,cnt;
inline ll power(ll a,ll b)
{
    ll ans=1;
    for(;b;b>>=1,a=a*a%mod)
        if(b&1)ans=ans*a%mod;
    return ans;
}
void tarjan(int now,int last)
{
    dfn[now]=low[now]=++tot; st[++tp]=now;
    for(int i=G.fir[now];~i;i=G.e[i].nxt)
        if(i!=(last^1)){
            if(!dfn[G.e[i].to]){
                tarjan(G.e[i].to,i);
                low[now]=std::min(low[now],low[G.e[i].to]);
                if(low[G.e[i].to]>=dfn[now]){
                    ++cnt;
                    T.add_edge(now,cnt); T.add_edge(cnt,now);
                    do{
                        T.add_edge(st[tp],cnt); T.add_edge(cnt,st[tp]);
                    }while(st[tp--]!=G.e[i].to);
                }
            }
            else low[now]=std::min(low[now],dfn[G.e[i].to]);
        }
}
void dfs(int now,int root)
{
    rt[now]=root;
    size[now]=val[now];
    for(int i=T.fir[now];~i;i=T.e[i].nxt)
        if(T.e[i].to!=fa[now]){
            fa[T.e[i].to]=now;
            dfs(T.e[i].to,root);
            size[now]+=size[T.e[i].to];
        }
}
void work()
{
    n=read(); m=read();
    G.clear();
    for(int i=1;i<=m;i++){
        int x=read(),y=read();
        G.add_edge(x,y); G.add_edge(y,x);
    }
    scanf("%s",s);
    memset(val,0,sizeof(val));
    for(int i=1;i<=n;i++)
        val[i]=(s[i-1]=='1');
    T.clear();
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    tot=tp=0; cnt=n;
    for(int i=1;i<=n;i++)
        if(!dfn[i]){
            tarjan(i,-1);
            fa[i]=-1;
            dfs(i,i);
        }
    int odd=0,block=0;
    for(int i=1;i<=n;i++)
        if(fa[i]==-1)odd+=(size[i]&1),++block;
    ans[0]=(odd?0:power(2,m-n+block));
    for(int i=1;i<=n;i++){
        odd-=(size[rt[i]]&1);
        int flag=1;
        for(int j=T.fir[i];~j;j=T.e[j].nxt)
            if(T.e[j].to!=fa[i]&&(size[T.e[j].to]&1)){
                flag=0; break;
            }
        if(odd||!flag||((size[rt[i]]-size[i])&1))ans[i]=0;
        else ans[i]=power(2,(m-G.deg[i])-(n-1)+(block+T.deg[i]-1));
        odd+=(size[rt[i]]&1);
    }
    for(int i=0;i<=n;i++)
        writesp(ans[i]);
    putchar('\n');
}
int main()
{
    int T=read();
    while(T--)work();
    return 0;
}
反色游戏

 

posted @ 2019-09-07 21:24  QuartZ_Z  阅读(131)  评论(0编辑  收藏