【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;
}

反色游戏

原文地址:https://www.cnblogs.com/quzhizhou/p/11483149.html

时间: 2024-10-09 18:22:04

【loj#2524】【bzoj5303】 [Haoi2018]反色游戏(圆方树)的相关文章

[SDOI2018]战略游戏 圆方树,树链剖分

[SDOI2018]战略游戏 这题是道路相遇(题解)的升级版,询问的两个点变成了\(S\)个点. LG传送门 还是先建出圆方树,考虑对于询问的\(S\)个点,答案就是圆方树上能包含这些点的最小连通块中的圆点个数减去\(S\).问题变成了怎样求这样的连通块中的圆点个数,直接给结论吧:先搞出树的dfs序,把询问的点按dfs序从小到大排一遍序,每次把答案加上第\(i\)和第\(i + 1\)个点之间的圆点个数,但是不算lca,再加上第\(1\)个和第\(S\)个点之间的圆点个数,然后除以二就得到了这个

[HAOI2018] 反色游戏

做法超好想,细节调一辈子. 估计这句话最适合这个题了hhhh. 首先一个很显然的想法:把边设成未知数,对点列异或方程,最后的解的个数就是 2^自由元 . 不过如果某个联通分量里有奇数个黑点,那么问题无解.然后我来证明一下: 把一个联通分量里所有点代表的方程都异或起来,因为这个联通分量里的边恰好在其两个端点的行是1,所以最后异或出来的方程前m列都是0,如果有奇数个黑点的话,最后一列就是1,那么无解(不能0=1吧hhh). 直接高斯消元是 O(N^3) 的,肯定要gg了啊 (不过竟然给了60分消元h

圆方树学习

圆方树是一种数据结构. 这个东西原始的出处应该是paper <Maintaining bridge-connected and biconnected components on-line> tarjan和另外一个人写的...当时叫forest data structure 然后这个东西似乎已经流行很久了?http://blog.csdn.net/PoPoQQQ/article/details/49513819 cjk大爷最近发了一篇博客写这个:http://immortalco.blog.u

【BZOJ】2125: 最短路 圆方树(静态仙人掌)

[题意]给定带边权仙人掌图,Q次询问两点间最短距离.n,m,Q<=10000 [算法]圆方树处理仙人掌问题 [题解]树上的两点间最短路问题,常用倍增求LCA解决,考虑扩展到仙人掌图. 先对仙人掌图建圆方树,圆圆边和原图边权一致.对于每个方点代表的环,记深度最小的点为x,则圆方边的边权是圆点到x的最短距离. 若lca(u,v)为圆点,则两点间最短路转化为圆方树上dis[u]+dis[v]-2*dis[lca].(向上延伸的路径,经过环则必然经过每个方点的x,计算无误) 若lca(u,v)为方点,则

CF487E Tourists 【圆方树 + 树剖 + 堆】

题目链接 CF487E 题解 圆方树 + 树剖 裸题 建好圆方树维护路径上最小值即可 方点的值为其儿子的最小值,这个用堆维护 为什么只维护儿子?因为这样修改点的时候就只需要修改其父亲的堆 这样充分利用了一对一的特性优化了复杂度 如此询问时如果\(lca\)为方点,再询问一下\(lca\)的父亲即可 复杂度\(O(qlog^2n)\) #include<algorithm> #include<iostream> #include<cstring> #include<

【P4320】 道路相遇 (圆方树+LCA)

题目链接 题意:给一张无向图和\(M\)个询问,问\(u,v\)之间的路径的必经之点的个数. 对图建出圆方树,然后必经之点就是两点路径经过的原点个数,用\((dep[u]+dep[v]-dep[LCA]*2)/2+1\)即可算出. 什么你不知道圆方树(说的跟我知道一样) \(APIO2018\)出来的黑科技,详见\(APIO2018\)铁人两项. 就是对每个点双新建一个点,然后让点双里所有点都对这个点连边. 看图. #include <cstdio> const int MAXN = 5000

Tourists——圆方树

CF487E Tourists 一般图,带修求所有简单路径代价. 简单路径,不能经过同一个点两次,那么每个V-DCC出去就不能再回来了. 所以可以圆方树,然后方点维护一下V-DCC内的最小值. 那么,从任意一个割点进入这个DCC,必然可以绕一圈再从另一个割点出去. 所以,路径上的最小值,就是圆方树路径上的最小值.方点的最小值就是在这个DCC中走一走得到的. 树链剖分+线段树维护路径 用堆维护方点四周的圆点的最小值.然后更新. 一个问题是: 更新一个割点圆点,会影响到四周所有的方点.暴力更新,菊花

CF487E Tourists 圆方树、树链剖分

传送门 注意到我们需要求的是两点之间所有简单路径中最小值的最小值,那么对于一个点双联通分量来说,如果要经过它,则一定会经过这个点双联通分量里权值最小的点 注意:这里不能缩边双联通分量,样例\(2\)就是一个反例 上面这个图如果缩点双会缩成\(3\)个,但是缩边双会将整个图缩成\(1\)个点. 假如我们询问的是\((1,4)\)之间的简单路径,而图中权值最小的点为\(7\)号点,那么如果缩成了边双联通分量,你的答案会是\(7\)号点的权值,意即认为可以走到\(7\)号点,但实际上如果到\(7\)号

道路相遇 圆方树

道路相遇 基础圆方树. 不会圆方树看我另一篇文章 LG传送门 发现必经之路上的点一定是简单路径上的点,可以自己手玩.处理无向图上的简单路径,考虑把圆方树建出来,发现答案就是园方树上两点间圆点个数.由于广义园方树上圆方点相间,可以用深度表示答案,发现答案就是\((dep[u] + dep[v] - 2 * dep[lca]) / 2 + 1\). #include <cstdio> #include <cctype> #include <vector> #define R