【bzoj1977】[BeiJing2010組隊]次小生成樹 Tree 權值線段樹合並
阿新 • • 發佈:2017-10-23
for 端點 light 輸出 pan 是否 題目 find upd
題目描述
求一張圖的嚴格次小生成樹的邊權和,保證存在。
輸入
第一行包含兩個整數N 和M,表示無向圖的點數與邊數。 接下來 M行,每行 3個數x y z 表示,點 x 和點y之間有一條邊,邊的權值為z。
輸出
包含一行,僅一個數,表示嚴格次小生成樹的邊權和。(數據保證必定存在嚴格次小生成樹)
樣例輸入
5 6
1 2 1
1 3 2
2 4 3
3 5 4
3 4 3
4 5 6
樣例輸出
11
題解
權值線段樹合並
首先有一個常用的結論:次小生成樹(無論是否嚴格)只要存在,則一定可以由最小生成樹僅改變一條邊構成,並且添加的邊一定能覆蓋刪除的邊。
然後考慮刪除哪條邊或者加入哪條邊均可。
然後只會寫數據結構的傻逼GXZ的做法比較naive:考慮每一條非樹邊的貢獻,給兩個端點打加入標記,給LCA打刪除標記,自底向上跑權值線段樹合並,維護出現過的最小權值和次小權值,然後搜到每條邊時用最小的不相等的權值更新答案。
正解貌似是考慮加入哪條非樹邊,倍增找最小次小值?不管了反正512MB內存能過。。。
時間復雜度$O(n\log n)$
#include <cstdio> #include <cstring> #include <algorithm> #define N 100010 #define M 1000000000 #define inf 0x7f7f7f7f using namespace std; struct data { int x , y , z; bool operator<(const data &a)const {return z < a.z;} }a[N * 3]; int f[N] , flag[N * 3] , head[N] , to[N << 1] , len[N << 1] , next[N << 1] , cnt , fa[N][20] , deep[N] , val[N] , log[N]; int ls[N << 7] , rs[N << 7] , si[N << 7] , mx[N << 7] , sx[N << 7] , tot , root[N] , ans = inf; int find(int x) { return x == f[x] ? x : f[x] = find(f[x]); } inline void add(int x , int y , int z) { to[++cnt] = y , len[cnt] = z , next[cnt] = head[x] , head[x] = cnt; } void dfs(int x) { int i; for(i = 1 ; (1 << i) <= deep[x] ; i ++ ) fa[x][i] = fa[fa[x][i - 1]][i - 1]; for(i = head[x] ; i ; i = next[i]) if(to[i] != fa[x][0]) fa[to[i]][0] = x , deep[to[i]] = deep[x] + 1 , val[to[i]] = len[i] , dfs(to[i]); } inline int lca(int x , int y) { int i; if(deep[x] < deep[y]) swap(x , y); for(i = log[deep[x] - deep[y]] ; ~i ; i -- ) if(deep[x] - deep[y] >= (1 << i)) x = fa[x][i]; if(x == y) return x; for(i = log[deep[x]] ; ~i ; i -- ) if(deep[x] >= (1 << i) && fa[x][i] != fa[y][i]) x = fa[x][i] , y = fa[y][i]; return fa[x][0]; } void pushup(int x) { if(mx[ls[x]] == inf) mx[x] = mx[rs[x]] , sx[x] = sx[rs[x]]; else if(sx[ls[x]] == inf) mx[x] = mx[ls[x]] , sx[x] = mx[rs[x]]; else mx[x] = mx[ls[x]] , sx[x] = sx[ls[x]]; } void update(int p , int a , int l , int r , int &x) { if(!x) x = ++tot; if(l == r) { si[x] += a; if(si[x] > 0) mx[x] = l; else mx[x] = inf; return; } int mid = (l + r) >> 1; if(p <= mid) update(p , a , l , mid , ls[x]); else update(p , a , mid + 1 , r , rs[x]); pushup(x); } int merge(int l , int r , int x , int y) { if(!x) return y; if(!y) return x; if(l == r) { si[x] += si[y]; if(si[x] > 0) mx[x] = l; else mx[x] = inf; return x; } int mid = (l + r) >> 1; ls[x] = merge(l , mid , ls[x] , ls[y]); rs[x] = merge(mid + 1 , r , rs[x] , rs[y]); pushup(x); return x; } void solve(int x) { int i; for(i = head[x] ; i ; i = next[i]) if(to[i] != fa[x][0]) solve(to[i]) , root[x] = merge(1 , M , root[x] , root[to[i]]); if(mx[root[x]] != inf) { if(mx[root[x]] != val[x]) ans = min(ans , mx[root[x]] - val[x]); else if(sx[root[x]] != inf) ans = min(ans , sx[root[x]] - val[x]); } } int main() { int n , m , i; long long sum = 0; scanf("%d%d" , &n , &m); for(i = 1 ; i <= m ; i ++ ) scanf("%d%d%d" , &a[i].x , &a[i].y , &a[i].z); sort(a + 1 , a + m + 1); log[0] = -1; for(i = 1 ; i <= n ; i ++ ) f[i] = i , log[i] = log[i >> 1] + 1; for(i = 1 ; i <= m ; i ++ ) if(find(a[i].x) != find(a[i].y)) f[f[a[i].x]] = f[a[i].y] , add(a[i].x , a[i].y , a[i].z) , add(a[i].y , a[i].x , a[i].z) , sum += a[i].z , flag[i] = 1; dfs(1); memset(mx , 0x7f , sizeof(mx)) , memset(sx , 0x7f , sizeof(sx)); for(i = 1 ; i <= m ; i ++ ) if(!flag[i]) update(a[i].z , 1 , 1 , M , root[a[i].x]) , update(a[i].z , 1 , 1 , M , root[a[i].y]) , update(a[i].z , -2 , 1 , M , root[lca(a[i].x , a[i].y)]); solve(1); printf("%lld\n" , sum + ans); return 0; }
【bzoj1977】[BeiJing2010組隊]次小生成樹 Tree 權值線段樹合並