【XSY3345】生成树 并查集

题目大意

  有一个两部各有 \(n\) 个节点的二分图 \(G\),定义 \(G^m\) 为一个 \(m+1\) 层的图,每层有 \(n\) 个节点,相邻两层的诱导子图都和 \(G\) 相同。

  给你 \(m\),求对于所有 \(1\leq i\leq m\)\(G^i\) 的最小生成树的边权和。

  保证图连通。

  \(n,m\leq 100000,\text{边数 }\leq 200000,\text{边权}\leq 30\)

题解

  对于 \(G^i\),先求出用了多少种边权 \(<j\) 的边,再求出用了多少条边权 \(\leq j\) 的边,就可以得到用了多少条边权为 \(j\) 的边。

  那么边权就可以忽略了。

  现在要求出 \(G^i\) 有多少条边。

  从左往右扫,用并查集维护最后两层节点的连通性。

  那么再下一层的并查集肯定会是这两层的并查集加上一点边。

  当我们处理完一层的时候,求出这层新加的边对下一层的贡献。

  这层每加一条边,下一层就要在这两个集合右侧的点之间连边。

  然后不停地往右边传就好了。

  每加一条边就会合并两个集合,所以总共会加 \(O(n)\) 条边。

  时间复杂度:\(O(w(n+m+e)\alpha(n))\)

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<ctime>
#include<functional>
#include<cmath>
#include<vector>
#include<assert.h>
//using namespace std;
using std::min;
using std::max;
using std::swap;
using std::sort;
using std::reverse;
using std::random_shuffle;
using std::lower_bound;
using std::upper_bound;
using std::unique;
using std::vector;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
typedef std::pair<int,int> pii;
typedef std::pair<ll,ll> pll;
void open(const char *s){
#ifndef ONLINE_JUDGE
    char str[100];sprintf(str,"%s.in",s);freopen(str,"r",stdin);sprintf(str,"%s.out",s);freopen(str,"w",stdout);
#endif
}
void open2(const char *s){
#ifdef DEBUG
    char str[100];sprintf(str,"%s.in",s);freopen(str,"r",stdin);sprintf(str,"%s.out",s);freopen(str,"w",stdout);
#endif
}
int rd(){int s=0,c,b=0;while(((c=getchar())<'0'||c>'9')&&c!='-');if(c=='-'){c=getchar();b=1;}do{s=s*10+c-'0';}while((c=getchar())>='0'&&c<='9');return b?-s:s;}
void put(int x){if(!x){putchar('0');return;}static int c[20];int t=0;while(x){c[++t]=x%10;x/=10;}while(t)putchar(c[t--]+'0');}
int upmin(int &a,int b){if(b<a){a=b;return 1;}return 0;}
int upmax(int &a,int b){if(b>a){a=b;return 1;}return 0;}
const int N=200010;
int f[N];
vector<pii> g[40],a,b;
int find(int x)
{
    return f[x]==x?x:f[x]=find(f[x]);
}
int c[N];
int merge(int x,int y)
{
    if(find(x)==find(y))
        return 0;
    if(!c[find(y)])
        c[find(y)]=c[find(x)];
    f[find(x)]=find(y);
    return 1;
}
int n,m,e;
ll ans[N];
ll s[N];
ll d[N];
int main()
{
    open("c");
    scanf("%d%d%d",&n,&m,&e);
    int x,y,w;
    for(int i=1;i<=e;i++)
    {
        scanf("%d%d%d",&x,&y,&w);
        g[w].push_back(pii(x,y));
    }
    for(int i=1;i<=30;i++)
    {
        a.clear();
        for(int j=1;j<=i;j++)
            for(auto v:g[j])
                a.push_back(v);
        for(int j=1;j<=2*n;j++)
            f[j]=j;
        for(int j=1;j<=m;j++)
            d[j]=0;
        for(int j=1;j<=2*n;j++)
            c[j]=0;
        for(auto v:a)
            d[1]+=merge(v.first,v.second+n);
        b.clear();
        for(int j=n+1;j<=2*n;j++)
            if(!c[find(j)])
                c[find(j)]=j;
            else
                b.push_back(pii(c[find(j)]-n,j-n));
        for(int j=2;j<=m;j++)
        {
            a=b;
            b.clear();
            for(auto v:a)
                if(find(v.first)!=find(v.second))
                {
                    if(c[find(v.first)]&&c[find(v.second)])
                        b.push_back(pii(c[find(v.first)]-n,c[find(v.second)]-n));
                    merge(v.first,v.second);
                }
                else
                    d[j]--;
        }
        for(int j=2;j<=m;j++)
            d[j]+=d[j-1];
        for(int j=2;j<=m;j++)
            d[j]+=d[j-1];
        for(int j=1;j<=m;j++)
        {
            ans[j]+=i*(d[j]-s[j]);
            s[j]=d[j];
        }
    }
    for(int i=1;i<=m;i++)
        printf("%lld\n",ans[i]);
    return 0;
}
posted @ 2018-12-29 08:13 ywwyww 阅读(...) 评论(...) 编辑 收藏