1. 程式人生 > >【XSY3345】生成樹 並查集

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