• [题解] 铺路


    做法

    结论

    如果图的所有连通块的点数都是偶数,则可以选出一个生成子图,满足点的度数均为奇数。

    首先因为度数和是偶数,所以如果每个点的度数是奇数,点数一定是偶数。

    然后在一个偶数连通块内任选一个有根生成树,自下而上地对于度数为偶数的点删去它与父亲的连边。注意到度数和是偶数,而除根之外的所有点度数都被调成了奇数,所以根的度数也是奇数。这样就可以构造出方案。

    维护

    如果是静态,可以将边排序后一次加入并查集,维护连通块的 (size),也可以二分答案。

    对于动态的情况,可以写 LCT 维护子树的 (size),如果加入的边和原有的边冲突,就删掉最大的。这是个好做法,但不会写 LCT,扔了,复杂度 (mathcal O(m log n))

    答案是单调不增的(记 (-1)(+infty)),考虑分治。设当前要求解答案的区间为 ([l, r]),答案的范围是 ([low, high])

    先求解中心 (mid) 处的答案 (res_{mid}),然后分治到 (solve(l, mid - 1, res_{mid}, high), solve(mid + 1, r, low, res_{mid})) 两块求解。

    记每条边两个属性,(p, w)(p) 是位置, (w) 是权值,画在坐标系上,则每次的分治区间就是一个矩形。

    (solve(l, r, low, high)) 时,(p < l, w < low) 的已经在并查集中。

    先加入 (p in [l, mid]) 的边,然后依次加入 (p in [1, mid], w ge low) 的边,这里做法是 (p in [l, mid]) 排序,(p < l) 用一个 std::set 维护。

    求解出 (mid) 处的答案后,分治即可,注意分治前要始终保证 ((l, low)) 左下角的边已经被加入并查集。每次加入完后,要撤销并查集操作。

    复杂度

    对于加入 ([l, mid]) 内的边,容易分析其复杂度为 (mathcal O(m log m log n)),并查集不能路径压缩,复杂度 (mathcal O(log n))

    对于从 std::set 中加入的边(即每次 (p < mid, w in [low, res_{mid}]) 的边):分开考虑每条边插入的次数。从第一次加入它的极大分治区间开始考虑(只会有一个),则它分治下去的两个区间中只会有一个有可能插入这条边,因此一条边最多插入 (mathcal O(log m)) 次。

    所以貌似整体二分复杂度也是对的,可能常数还小一些

    总复杂度 (mathcal O(n log m log n))

    Code

    #include <cstdio>
    #include <cctype>
    #include <cstring>
    #include <algorithm>
    #include <set>
    using namespace std;
    #define File(s) freopen(s".in", "r", stdin), freopen(s".out", "w", stdout)
    typedef long long ll;
    namespace io {
    	const int SIZE = (1 << 21) + 1;
    	char ibuf[SIZE], *iS, *iT, obuf[SIZE], *oS = obuf, *oT = oS + SIZE - 1, c, qu[55]; int f, qr;
    	#define gc() (iS == iT ? (iT = (iS = ibuf) + fread (ibuf, 1, SIZE, stdin), (iS == iT ? EOF : *iS ++)) : *iS ++)
    	char getc () {return gc();}
    	inline void flush () {fwrite (obuf, 1, oS - obuf, stdout); oS = obuf;}
    	inline void putc (char x) {*oS ++ = x; if (oS == oT) flush ();}
    	template <class I> inline void gi (I &x) {for (f = 1, c = gc(); c < '0' || c > '9'; c = gc()) if (c == '-') f = -1;for (x = 0; c <= '9' && c >= '0'; c = gc()) x = x * 10 + (c & 15); x *= f;}
    	template <class I> inline void print (I x) {if (!x) putc ('0'); if (x < 0) putc ('-'), x = -x;while (x) qu[++ qr] = x % 10 + '0',  x /= 10;while (qr) putc (qu[qr --]);}
    	struct Flusher_ {~Flusher_(){flush();}}io_flusher_;
    }
    using io :: gi; using io :: putc; using io :: print; using io :: getc;
    template<class T> void upmax(T &x, T y){x = x>y ? x : y;}
    template<class T> void upmin(T &x, T y){x = x<y ? x : y;}
    
    const int N = 300005, lgN = 20;
    
    int fa[N], rk[N], sz[N], oddc;
    int top = 0, stkx[N * lgN], stky[N * lgN], stkrk[N * lgN], stksz[N * lgN], stkodd[N * lgN];
    int n, m, wc;
    
    int find(int x){
    	for(; x != fa[x]; x = fa[x]) ;
    	return x;
    }
    void merge(int x, int y){
    	int fx = find(x), fy = find(y);
    	if(fx == fy) return ;
    	++top;
    	if(rk[fx] < rk[fy]) swap(fx, fy);
    	stkx[top] = fx; stky[top] = fy; stkrk[top] = rk[fx];
    	stkodd[top] = oddc; stksz[top] = sz[fx];
    	fa[fy] = fx; rk[fx] += rk[fx] == rk[fy];
    	oddc -= (sz[fx] & 1) + (sz[fy] & 1);
    	sz[fx] += sz[fy];
    	oddc += sz[fx] & 1;
    }
    void recover(int bot){
    	for(; top != bot; --top){
    		fa[stky[top]] = stky[top];
    		rk[stkx[top]] = stkrk[top];
    		sz[stkx[top]] = stksz[top];
    		oddc = stkodd[top];
    	}
    }
    
    int res[N];
    int wt[N];
    struct Item{
    	int u, v, w;
    	Item(){}
    	Item(int _u, int _v, int _w) : u(_u), v(_v), w(_w) {}
    }a[N];
    bool operator<(const Item &p, const Item &q) {
    	if(p.w == q.w)
    		return p.u == q.u ? p.v < q.v : p.u < q.u;
    	return p.w < q.w;
    }
    
    set<Item> S;
    
    void solve(int l, int r, int low, int high){
    	if(l > r || low == high){
    		for(int i=l; i<=r; i++){
    			res[i] = low;
    			S.insert(a[i]);
    		}
    		return ;
    	}
    	int mid = (l + r) >> 1;
    	static Item pt[N]; int pc = 0;
    	int bot = top;
    	for(int i=l; i<=mid; i++)
    		if(a[i].w <= low)
    			merge(a[i].u, a[i].v);
    		else if(a[i].w <= high)
    			pt[pc++] = a[i];
    	sort(pt, pt + pc);
    	auto lp = S.lower_bound(Item(0, 0, low)), hp = S.upper_bound(Item(1000000000, 1000000000, high));
    	const auto Lp = lp;
    	int ap = 0;
    	res[mid] = oddc ? high : low;
    	while(oddc && lp != hp && ap != pc){
    		if(lp->w < pt[ap].w){
    			res[mid] = lp->w;
    			merge(lp->u, lp->v);
    			++lp;
    		} else {
    			res[mid] = pt[ap].w;
    			merge(pt[ap].u, pt[ap].v);
    			++ap;
    		}
    	}
    	if(oddc){
    		while(oddc && lp != hp){
    			res[mid] = lp->w;
    			merge(lp->u, lp->v);
    			++lp;
    		}
    		while(oddc && ap != pc){
    			res[mid] = pt[ap].w;
    			merge(pt[ap].u, pt[ap].v);
    			++ap;
    		}
    		if(oddc) res[mid] = wc + 1;
    	}
    
    	recover(bot);
    	for(auto it = Lp; it != hp && it->w < res[mid]; ++it) merge(it->u, it->v);
    	solve(l, mid - 1, res[mid], high);
    
    	recover(bot);
    	S.insert(a[mid]);
    	for(int i=l; i<=mid; i++)
    		if(a[i].w < low)
    			merge(a[i].u, a[i].v);
    	solve(mid + 1, r, low, res[mid]);
    	recover(bot);
    }
    
    int main(){
    	gi(n); gi(m);
    
    	for(int i=1; i<=n; i++) fa[i] = i, sz[i] = 1;
    	oddc = n;
    
    	for(int i=1; i<=m; i++)
    		gi(a[i].u), gi(a[i].v), gi(a[i].w), wt[i] = a[i].w;
    	sort(wt + 1, wt + 1 + m);
    	wc = unique(wt + 1, wt + 1 + m) - wt - 1;
    	wt[wc + 1] = -1;
    	for(int i=1; i<=m; i++)
    		a[i].w = lower_bound(wt + 1, wt + 1 + wc, a[i].w) - wt;
    
    	solve(1, m, 1, m + 1);
    	for(int i=1; i<=m; i++)
    		print(wt[res[i]]), putc('
    ');
    	return 0;
    }
    
  • 相关阅读:
    异常处理学习笔记
    android 测试
    android 创建快捷方式
    POJ 3320 尺取法(基础题)
    HDOJ 1260 DP
    数位DP练习
    P2727 Stringsobits
    poj 2229 DP
    Canada Cup 2016 C. Hidden Word
    hdoj 1231 最大连续子列和
  • 原文地址:https://www.cnblogs.com/RiverHamster/p/sol-oj4124.html
Copyright © 2020-2023  润新知