BZOJ5303: [Haoi2018]反色游戏

BZOJ5303: [Haoi2018]反色游戏

https://lydsy.com/JudgeOnline/problem.php?id=5303

分析:

  • 如果\(1\)的个数为奇数,肯定无解。
  • 否则,如果是一棵树,答案为\(1\)
  • 否则,多出的那些边不会影响答案,答案为\(2^{n-m-1}\)
  • 若干个连通块需要乘起来,设连通块有\(tot\)个,那么答案就是\(2^{n-m-tot}\)
  • 分析删点后的影响。
    1. 孤立点,由于会被当做非割点需要单独拿出来特判。
    1. 非割点,没啥可说的,直接用公式计算即可。
    1. 割点,由于删去后需要重新计算连通块数和每个连通块内\(1\)的个数,我这里用圆方树来处理每个割点,在圆方树上可能会比较好理解,在原图上删掉这个点和在圆方树上删掉这个点是等价的。
  • 具体实现看代码,代码写的比较恶心。

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#include <vector>
using namespace std;
#define N 200050
#define mod 1000000007
#define mem(x) memset(x,0,sizeof(x))
typedef long long ll;
vector<int>V[N];
int head[N],to[N<<1],nxt[N<<1],cnt,n,m;
int dfn[N],low[N],S[N<<1],tp,vis[N<<1],bl[N],iscut[N],du[N],bcc,fa[N],tot,siz[N],se[N],sw[N];
char w[N];
ll totans,tans[N],ans[N],mi[N];
ll qp(ll x,ll y) {
    ll re=1;for(;y;y>>=1,x=x*x%mod) if(y&1) re=re*x%mod; return re;
}
inline void add(int u,int v) {
    to[++cnt]=v; nxt[cnt]=head[u]; head[u]=cnt;
}
void tarjan(int x,int rt) {
    int i,num=0;
    fa[x]=tot;
    siz[tot]++;
    sw[tot]+=(w[x]=='1');
    dfn[x]=low[x]=++dfn[0];
    for(i=head[x];i;i=nxt[i]) if(!vis[i]) {
        S[++tp]=i;
        num++;
        se[tot]++;
        vis[i]=vis[i^1]=1;
        if(!dfn[to[i]]) {
            tarjan(to[i],x);
            low[x]=min(low[x],low[to[i]]);
            if(low[to[i]]>=dfn[x]) {
                bcc++; V[bcc].clear();
                int t=0;
                iscut[x]=1;
                while(t!=i) {
                    t=S[tp--];
                    int u=to[t], v=to[t^1];
                    if(bl[u]!=bcc) bl[u]=bcc,V[bcc].push_back(u);
                    if(bl[v]!=bcc) bl[v]=bcc,V[bcc].push_back(v);
                }
            }
        }else low[x]=min(low[x],dfn[to[i]]);
    }
    if(x==rt&&num<2) iscut[rt]=0;
}
int hme,flg[N],TOT;
void dfs(int x,int y) {
    int i,cc=0; vis[x]=1;
    if(x<=n) siz[x]=w[x]=='1';
    else siz[x]=0;
    for(i=head[x];i;i=nxt[i]) if(to[i]!=y) {
        dfs(to[i],x);
        siz[x]+=siz[to[i]];
        if(siz[to[i]]&1) {
            flg[x]=1;
        }
        cc++;
    }
    if(x>n) return ;
    if(y) cc++;
    if((sw[fa[x]]-siz[x])&1) flg[x]=1;
    if(hme-(sw[fa[x]]&1)>0) flg[x]=1;
    if(iscut[x]) {
        if(flg[x]) ans[x]=0;
        else {
            ans[x]=mi[m-du[x]-n+1+cc+tot-1];
        }
    }
}
void solve() {
    memset(head,0,sizeof(head)); cnt=1;
    memset(dfn,0,sizeof(dfn));
    memset(vis,0,sizeof(vis));
    memset(bl,0,sizeof(bl));
    memset(iscut,0,sizeof(iscut));
    bcc=0;
    memset(siz,0,sizeof(siz));
    memset(se,0,sizeof(se));
    memset(sw,0,sizeof(sw));
    memset(du,0,sizeof(du));
    memset(flg,0,sizeof(flg));
    mem(fa);
    TOT=0; tot=0; tp=0; hme=0;
    scanf("%d%d",&n,&m);
    int i,x,y,j;
    int lm=max(n,m);
    for(mi[0]=i=1;i<=lm;i++) mi[i]=mi[i-1]*2%mod;
    for(i=1;i<=m;i++) {
        scanf("%d%d",&x,&y);
        add(x,y); add(y,x); du[x]++; du[y]++;
    }
    scanf("%s",w+1);
    for(i=1;i<=n;i++) if(!dfn[i]) {
        tot++; tarjan(i,i);
    }
    totans=1;
    for(i=1;i<=tot;i++) {
        if(sw[i]&1) tans[i]=0,hme++;
        else {
            tans[i]=mi[se[i]-siz[i]+1];
            totans=totans*tans[i]%mod;
        }
    }
    if(hme) ans[0]=0;
    else ans[0]=totans;
    for(x=1;x<=n;x++) {
        if(siz[fa[x]]==1) {
            if(hme-(sw[fa[x]]&1)>0) ans[x]=0;
            else ans[x]=totans;
            continue;
        }
        if(!iscut[x]) {
            if(hme-(sw[fa[x]]&1)>0) ans[x]=0;
            else if((sw[fa[x]]-(w[x]=='1'))&1) ans[x]=0;
            else ans[x]=mi[m-du[x]-n+1+tot];
        }
    }
    memset(head,0,sizeof(head)); cnt=0;
    for(i=1;i<=bcc;i++) {
        int lim=V[i].size();
        for(j=0;j<lim;j++) {
            add(V[i][j],i+n);
            add(i+n,V[i][j]);
        }
    }
    memset(siz,0,sizeof(siz));
    mem(vis);
    int ln=n+bcc;
    for(i=1;i<=n;i++) TOT+=w[i]=='1';
    for(i=1;i<=ln;i++) if(!vis[i]) {
        dfs(i,0);
    }
    for(i=0;i<=n;i++) printf("%lld ",ans[i]); puts("");
}
int main() {
    int T;
    scanf("%d",&T);
    while(T--) solve();
}

转载于:https://www.cnblogs.com/suika/p/10230160.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值