【XSY3345】生成樹 並查集
阿新 • • 發佈:2018-12-29
題目大意
有一個兩部各有 \(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; }