zoukankan      html  css  js  c++  java
  • [FJOI2020]染色图的联通性问题 题解

    FJOI2020 D1T2


    题目大意

    给出一个由 $n$ 个点 $m$ 条边构成的染色无向图,求删去每一个点及与其相连的边后图中不连通的同色点对数量。$n,mleq 10^5$。

    思路分析

    可以想到先统计原图的答案,然后对删去每个点后的多出的答案进行计算,输出时加上即可。

    原图的答案很容易统计,遍历一遍同时计算即可。

    如何统计删去每个点后多出的答案?模拟过后很容易发现,多出的答案就是删去这个点后断开的连通块之间形成的同色点对,只需要知道断开后每个连通块的各色点的数量即可。暴力统计显然复杂度太高,连最低档的分都拿不到。毒瘤FJOI

    可以想到用 tarjan 找删去后的各个连通块,统计用启发式合并或线段树合并。线段树合并实现简单但是空间较大,但是还是有神犇卡过去了。这里用的是线段树合并。

    合并的时候怎么计算呢?

    设当前已合并的连通块该颜色的点数和为 $x$ ,原连通块该颜色的点数为 $y$ ,那么遍历一个新的连通块时,设该连通块该颜色的点数为 $z$ ,则将该连通块合并后与其它部分断开后的贡献为为 $z*(y-x-z)+x*(y-x-z)=(x+z)(y-x-z)$ 。注意,若该连通块与原连通块之间会被断掉产生多出的答案,则需要加上 $x*z$ 。

    注意,上面的原连通块断开后即为当前节点的父节点所在的连通块。

    这样这道题就很好解了。对于原图中的每个连通块:

    1. 先遍历一遍,计算出连通块中每个颜色的点数
    2. 跑一遍 tarjan ,同时合并数据,计算断开连通块中的每个点后多出的答案
    3. 再遍历一遍,计算连通块与原图的其它连通块贡献的答案,然后将当前连通块的数据清空

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #define ll long long
    using namespace std;
    const int N=5e5+100;
    struct Seg
    {
        int lson,rson;
        ll val,sumv;
        #define lson(i) t[i].lson
        #define rson(i) t[i].rson
        #define val(i) t[i].val
        #define sumv(i) t[i].sumv
    }t[N*21];
    int n,m,tot,cnt,D;
    ll now,sum;
    int head[N],ver[2*N],Next[2*N];
    int rt[N],c[N],nowc[N],sumc[N],dfn[N],low[N];
    ll ans[N];
    bool vp[N],vq[N];
    void add(int x,int y)
    {
        ver[++tot]=y,Next[tot]=head[x],head[x]=tot;
        ver[++tot]=x,Next[tot]=head[y],head[y]=tot;
    }
    void change(int &p,int l,int r,int k)
    {
        if(!p)
            p=++cnt;
        if(l==r)
        {
            val(p)++,sumv(p)+=nowc[l]-1;
            return ;
        }
        int mid=l+r>>1;
        if(k<=mid)
            change(lson(p),l,mid,k);
        else
            change(rson(p),mid+1,r,k);
        sumv(p)=sumv(lson(p))+sumv(rson(p));
    }
    int merge(int x,int y,int l,int r)
    {
        if(!x)
            return y;
        if(!y)
            return x;
        if(l==r)
        {
            sumv(x)=(val(x)+val(y))*(nowc[l]-val(x)-val(y));//断开后的答案
            now+=val(x)*val(y);//当前合并的两个连通块断开的贡献
            val(x)+=val(y);
            return x;
        }
        int mid=l+r>>1;
        lson(x)=merge(lson(x),lson(y),l,mid);
        rson(x)=merge(rson(x),rson(y),mid+1,r);
        sumv(x)=sumv(lson(x))+sumv(rson(x));
        return x;
    }
    void pre(int x)
    {
        vp[x]=1;nowc[c[x]]++;
        for(int i=head[x];i;i=Next[i])
        {
            int y=ver[i];
            if(!vp[y])
                pre(y);
        }
    }//先遍历一遍,计算出连通块中每个颜色的点数
    void tarjan(int x)
    {
        int nowr=0;//临时根
        low[x]=dfn[x]=++cnt;
        change(rt[x],1,D,c[x]);
        for(int i=head[x];i;i=Next[i])
        {
            int y=ver[i];
            if(!dfn[y])
            {
                tarjan(y);
                low[x]=min(low[x],low[y]);
                if(low[y]>=dfn[x])//断开会使连通块断开,类似割点
                {
                    now=0;
                    rt[x]=merge(rt[x],rt[y],1,D);
                    ans[x]+=now;//多出的答案
                }
                else
                    nowr=merge(nowr,rt[y],1,D);//不会断开,合并到临时根上,避免多统计答案
            }
            else
                low[x]=min(low[x],dfn[y]);
        }
        ans[x]+=sumv(rt[x]);
        rt[x]=merge(rt[x],nowr,1,D);//合并临时根
    }//跑一遍 tarjan ,同时合并数据,计算断开连通块中的每个点后多出的答案
    void query(int x)
    {
        vq[x]=1;
        sum+=nowc[c[x]]*sumc[c[x]];//计算当前连通块与其它连通块的贡献
        sumc[c[x]]+=nowc[c[x]],nowc[c[x]]=0;
        for(int i=head[x];i;i=Next[i])
        {
            int y=ver[i];
            if(!vq[y])
                query(y);
        }
    }//再遍历一遍,计算当前连通块与原图的其它连通块贡献的答案,然后将当前连通块的数据清空
    int main()
    {
        //freopen("pair.in","r",stdin);
        //freopen("pair.out","w",stdout);
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
            scanf("%d",&c[i]),D=max(D,c[i]);
        for(int i=1,x,y;i<=m;i++)
        {
            scanf("%d%d",&x,&y);
            add(x,y);
        }
        for(int i=1;i<=n;i++)
            if(!dfn[i])
                pre(i),tarjan(i),query(i);
        for(int i=1;i<=n;i++)
            printf("%lld
    ",sum+ans[i]);
        return 0;
    }
  • 相关阅读:
    如何成为一名高级C++程序员
    JAVA设计模式:装饰模式
    JAVA设计模式:代理模式
    HTML学习笔记四
    HTML学习三
    HTML学习二
    HTML学习一
    JAVA集合一之集合简介(Collection,List,Set)
    20150817---成长日记1---DelayQueue&&Delayed&&Other
    shell-早间学习,每日一点-5
  • 原文地址:https://www.cnblogs.com/TEoS/p/13327040.html
Copyright © 2011-2022 走看看