[CODE FESTIVAL 2016 Final]G - Zigzag MST——MST
阿新 • • 發佈:2018-11-04
思路:
如果直接去連邊然後跑最小生成樹的話,不難發現邊數是\(O(nq)\)級別的。
於是我們可以觀察一下這一張圖:
不難發現每次新增的邊是相鄰的兩個點之間互相連邊,並且很重要的是,邊權一次一次地變大。
考慮Kruskal的過程,如果有兩條邊\((u_1,v_1,w_1),(u_2,v_2,w_2)\),並且\(w_1<w_2\),那麼可以肯定的是,在考慮第二條邊的時候,第一條邊的兩個點一定已經在一個連通塊裡面了。
再考慮一下Prim的過程,不難發現對於一個未加入當前連通塊的點,我們關心的只是這個點離連通塊的最小距離,這個時候它自己本身與哪個點相連已經不重要了。
於是整個演算法的大致思路已經出來了,對於一組連續的邊\((a,b,w_1),(b,c,w_2),w_1<w_2\)
不難發現這樣重新連邊之後所有的邊都是這樣的形式\((a,a+1,w)\),於是我們開一個數組記錄下來,之後再遞推一遍便可以得到環上的那\(O(n)\)條邊了。之後再做一遍Kruskal即可。
#include<bits/stdc++.h> #define REP(i,a,b) for(int i=a,i##_end_=b;i<=i##_end_;++i) #define DREP(i,a,b) for(int i=a,i##_end_=b;i>=i##_end_;--i) #define debug(x) cout<<#x<<"="<<x<<endl #define fi first #define se second #define mk make_pair #define pb push_back typedef long long ll; using namespace std; void File(){ freopen("gkk.in","r",stdin); freopen("gkk.out","w",stdout); } template<typename T>void read(T &_){ T __=0,mul=1; char ch=getchar(); while(!isdigit(ch)){ if(ch=='-')mul=-1; ch=getchar(); } while(isdigit(ch))__=(__<<1)+(__<<3)+(ch^'0'),ch=getchar(); _=__*mul; } const int maxn=2e5+10; const int maxm=1e6+10; int n,q,m; ll f[maxn]; struct edge{ int u,v; ll w; bool operator < (const edge & ano) const { return w<ano.w; } }E[maxm]; namespace Kruskal{ ll ans; int fa[maxn]; int find(int x){return fa[x]==x ? x : fa[x]=find(fa[x]);} void work(){ REP(i,0,n-1)fa[i]=i; sort(E+1,E+m+1); REP(i,1,m){ int u=E[i].u,v=E[i].v; if(find(u)==find(v))continue; fa[find(u)]=find(v); ans+=E[i].w; } printf("%lld\n",ans); } } int main(){ File(); read(n); read(q); memset(f,63,sizeof(f)); int u,v; ll w; REP(i,1,q){ read(u),read(v),read(w); E[++m]=(edge){u,v,w}; f[u]=min(f[u],w+1); f[v]=min(f[v],w+2); } REP(i,0,2*n-1){ int pre= !(i%n) ? n-1 : i%n-1; f[i%n]=min(f[i%n],f[pre]+2); } REP(i,0,n-1){ int nex=(i+1)%n; E[++m]=(edge){i,nex,f[i]}; } Kruskal::work(); return 0; }