zoukankan      html  css  js  c++  java
  • 学习笔记 / 刷题记录:高级数据结构

    习题

    方差

    拆式子,维护支持求区间平方和和区间和的线段树,记一个加法懒标记。

    #include <iostream>
    #include <cstdio>
    
    using namespace std;
    
    const int N = 100005;
    
    int n, m;
    
    double a[N], tag[N << 2], s1[N << 2], s2[N << 2];
    
    void inline pushup(int p) {
        s1[p] = s1[p << 1] + s1[p << 1 | 1];
        s2[p] = s2[p << 1] + s2[p << 1 | 1];
    }
    
    void pushdown(int p, int l, int mid, int r) {
        s2[p << 1] += (mid - l + 1) * tag[p] * tag[p] + 2 * s1[p << 1] * tag[p];
        s2[p << 1 | 1] += (r - mid) * tag[p] * tag[p] + 2 * s1[p << 1 | 1] * tag[p];
        s1[p << 1] += tag[p] * (mid - l + 1);
        s1[p << 1 | 1] += tag[p] * (r - mid);
        tag[p << 1] += tag[p], tag[p << 1 | 1] += tag[p];
        tag[p] = 0;
    }
    
    void build(int p, int l, int r) {
        if (l == r) {
            s1[p] = a[r], s2[p] = a[r] * a[r];
            return;
        }
        int mid = (l + r) >> 1;
        build(p << 1, l, mid);
        build(p << 1 | 1, mid + 1, r);
        pushup(p);
    }
    
    void change(int p, int l, int r, int x, int y, double k) {
        if (x <= l && r <= y) {
            s2[p] += (r - l + 1) * k * k + 2 * s1[p] * k;
            s1[p] += (r - l + 1) * k;
            tag[p] += k;
            return;
        }
        int mid = (l + r) >> 1;
        pushdown(p, l, mid, r);
        if (x <= mid) change(p << 1, l, mid, x, y, k);
        if (mid < y) change(p << 1 | 1, mid + 1, r, x, y, k);
        pushup(p);
    }
    
    double query(int p, int l, int r, int x, int y, int o) {
        if (x <= l && r <= y) 
            return o == 1 ? s1[p] : s2[p];
        int mid = (l + r) >> 1; double res = 0;
        pushdown(p, l, mid, r);
        if (x <= mid) res += query(p << 1, l, mid, x, y, o);
        if (mid < y) res += query(p << 1 | 1, mid + 1, r, x, y, o);
        return res;
    }
    
    int main() {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; i++) scanf("%lf", a + i);
        build(1, 1, n);
        while (m--) {
            int opt, x, y; scanf("%d%d%d", &opt, &x, &y);
            if (opt == 1) {
                double k; scanf("%lf", &k);
                change(1, 1, n, x, y, k);
            } else if (opt == 2) {
                printf("%.4f
    ", query(1, 1, n, x, y, 1) / (y - x + 1));
            } else {
                double s1 = query(1, 1, n, x, y, 1), s2 = query(1, 1, n, x, y, 2), p = s1 / (y - x + 1);
                printf("%.4f
    ", ((y - x + 1) * p * p + s2 - 2 * s1 * p) / (y - x + 1));
            }
        }
        return 0;
    }
    

    [NOI2016]区间

    按区间长度排序,双指针,([l, r]) 符合条件等价于将 ([l, r]) 的线段每段区间 + 1,单点最大值为 (m)

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    using namespace std;
    
    const int N = 500005, INF = 2e9;
    
    int n, m, d[N << 1], tot, val[N << 3], tag[N << 3];
    
    struct Seg{
    	int l, r, len;
    	bool operator < (const Seg &b) const {
    		return len < b.len;
    	}
    } s[N];
    
    int inline get(int x) {
    	return lower_bound(d + 1, d + 1 + tot, x) - d; 
    }
    
    void inline pushup(int p) {
    	val[p] = max(val[p << 1], val[p << 1 | 1]);
    }
    
    void inline pushdown(int p) {
    	if (tag[p]) {
    		val[p << 1] += tag[p], val[p << 1 | 1] += tag[p];
    		tag[p << 1] += tag[p], tag[p << 1 | 1] += tag[p];
    		tag[p] = 0;
    	}
    }
    
    void change(int p, int l, int r, int x, int y, int k) {
    	if (x <= l && r <= y) {
    		val[p] += k, tag[p] += k;
    		return ;
    	}
    	int mid = (l + r) >> 1;
    	pushdown(p);
    	if (x <= mid) change(p << 1, l, mid, x, y, k);
    	if (mid < y) change(p << 1 | 1, mid + 1, r, x, y, k);
    	pushup(p);
    }
    
    int main() {
    	scanf("%d%d", &n, &m);
    	for (int i = 1, l, r; i <= n; i++) {
    		scanf("%d%d", &l, &r);
    		s[i] = (Seg) { l, r, r - l };	
    		d[++tot] = l, d[++tot] = r;	
    	}
    	sort(s + 1, s + 1 + n);
    	sort(d + 1, d + 1 + tot);
    	tot = unique(d + 1, d + 1 + tot) - d - 1;
    	for (int i = 1; i <= n; i++)
    		s[i].l = get(s[i].l), s[i].r = get(s[i].r);
    	int ans = INF;
    	for (int i = 1, j = 1; i <= n; i++) {
    		change(1, 1, tot, s[i].l, s[i].r, 1);
    		while (j <= i && val[1] == m) {
    			ans = min(ans, s[i].len - s[j].len);
    			change(1, 1, tot, s[j].l, s[j].r, -1);
    			++j;
    		}
    	}
    	if (ans == INF) puts("-1");
    	else printf("%d
    ", ans);
    	return 0;
    }
    

    楼房重建

    很妙的分治讨论,通过划分右子树变成右左和右右,特殊的性质决定只用递归一层,

    #include <iostream>
    #include <cstdio>
    
    using namespace std;
    
    const int N = 100005;
    
    int n, m, cnt[N << 2];
    
    double val[N << 2];
    
    // > v 的限制 
    int find(int p, double v, int l, int r) {
    	if (l == r) return val[p] > v;
    	int mid = (l + r) >> 1;
    	if (val[p << 1] <= v) return find(p << 1 | 1, v, mid + 1, r);
    	else return find(p << 1, v, l, mid) + cnt[p] - cnt[p << 1];
    }
    
    void inline pushup(int p, int l, int mid, int r) {
    	val[p] = max(val[p << 1], val[p << 1 | 1]);
    	cnt[p] = cnt[p << 1] + find(p << 1 | 1, val[p << 1], mid + 1, r);
    }
    
    void change(int p, int l, int r, int x, int y) {
    	if (l == r) {
    		val[p] = (double)y / x, cnt[p] = 1;
    		return;
    	}
    	int mid = (l + r) >> 1;
    	if (x <= mid) change(p << 1, l, mid, x, y);
    	else change(p << 1 | 1, mid + 1, r, x, y);
    	pushup(p, l, mid, r);
    }
    
    int main() {
    	scanf("%d%d", &n, &m);
    	while (m--) {
    		int x, y; scanf("%d%d", &x, &y);
    		change(1, 1, n, x, y);
    		printf("%d
    ", cnt[1]);
    	}
    }
    

    [SCOI2015]情报传递

    对于 (x, y, c),当前时刻为 (t),相当于求 (t - c - 1) 时刻前这条路径上激活了多少点。

    用树状数组维护 (d)(d_i) 表示 (i) 到根路径上的点权和,每次单点修改 (Rightarrow) 子树修改。

    每次查询用四个点差分。

    #include <iostream>
    #include <cstdio>
    #include <vector>
    using namespace std;
    
    const int N = 200005;
    
    int n, m, fa[N], rt, top[N], son[N], c[N];
    int dfn[N], dfncnt, sz[N], dep[N], p[N], d[N], ans[N], T[N];
    
    struct Q{
    	int x, y, p, id;
    };
    
    vector<Q> q[N];
    
    int head[N], numE = 0;
    
    struct E{
    	int next, v;
    } e[N];
    
    void inline addEdge(int u, int v) {
    	e[++numE] = (E) { head[u], v };
    	head[u] = numE;
    }
    
    void dfs1(int u) {
    	sz[u] = 1;
    	for (int i = head[u]; i; i = e[i].next) {
    		int v = e[i].v;
    		if (v == fa[u]) continue;
    		dep[v] = dep[u] + 1;
    		dfs1(v);
    		sz[u] += sz[v];
    		if (sz[v] > sz[son[u]]) son[u] = v;
    	}
    }
    
    void dfs2(int u, int tp) {
    	top[u] = tp, dfn[u] = ++dfncnt;
    	if (son[u]) dfs2(son[u], tp);
    	for (int i = head[u]; i; i = e[i].next) {
    		int v = e[i].v;
    		if (v == fa[u] || v == son[u]) continue;
    		dfs2(v, v);
    	}
    }
    
    int inline lca(int x, int y) {
    	while (top[x] != top[y]) {
    		while (dep[top[x]] < dep[top[y]]) swap(x, y);
    		x = fa[top[x]];
    	}
    	return dep[x] < dep[y] ? x : y;	
    }
    
    void inline add(int x, int k) {
    	for (; x <= n; x += x & -x) c[x] += k;
    } 
    
    int inline ask(int x) {
    	int res = 0;
    	for (; x; x -= x & -x) res += c[x];
    	return res;
    }
    
    int main() {
    	scanf("%d", &n);
    	for (int i = 1; i <= n; i++) {
    		scanf("%d", fa + i);
    		if (!fa[i]) rt = i;
    		else addEdge(fa[i], i);
    	}
    	dep[rt] = 1;
    	dfs1(rt);
    	dfs2(rt, rt);
    	scanf("%d", &m);
    	for (int i = 1; i <= m; i++) {
    		int opt; scanf("%d", &opt);
    		if (opt == 1) {
    			int x, y, c; scanf("%d%d%d", &x, &y, &c);
    			p[i] = lca(x, y);
    			d[i] = dep[x] + dep[y] - dep[p[i]] - dep[fa[p[i]]];
    			if (i - c - 1 > 0) q[i - c - 1].push_back( (Q) { x, y, p[i], i } );
    		} else {
    			scanf("%d", T + i);
    		}
    	}
    	for (int i = 1; i <= m; i++) {
    		if (T[i]) 
    			add(dfn[T[i]], 1), add(dfn[T[i]] + sz[T[i]], -1);
    		for (int j = 0; j < q[i].size(); j++) {
    			Q u = q[i][j];
    			ans[u.id] = ask(dfn[u.x]) + ask(dfn[u.y]) - ask(dfn[u.p]) - ask(dfn[fa[u.p]]);
    		}
    	}
    	for (int i = 1; i <= m; i++)
    		if (p[i]) printf("%d %d
    ", d[i], ans[i]);
    	return 0;
    }
    

    [HNOI2011]括号修复 / [JSOI2011]括号序列

    括号序合法的另一种判定:

    • 每个前缀的 (() 数都大于等于 ()) 的个数
    • 每个后缀的 ()) 的个数都大于等于 (() 的个数。

    (() 视为 (-1)()) 视为 (1)

    (pre_i, suf_i) 分别为前后缀和

    合法条件是满足 (max(pre) le 0)(min(suf) ge 0)

    答案是 ( lceil frac{max(pre)}{2} ceil + lceil frac{-min(suf)}2 ceil)

    证明

    必要性:每次将右括号改成左括号会让右边的 (pre +2),因此至少要加 $ lceil frac{max(pre)}{2} ceil$ 才能让每个合法。后缀同理。

    充分性:考虑构造,对于 (pre) 来说,把从左往右 $ lceil frac{max(pre)}{2} ceil$ 个右括号改成左括号,对于 (pre_i) 至少 (i) 前面有 (-pre_i) 个左括号,所以改的时候一定能影响到 (i)。对于 (suf) 同理。

    因为要 swap,所以只能用平衡树维护区间和、前后缀最大值最小值。

    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #define ls t[p].l
    #define rs t[p].r
    using namespace std;
    
    const int N = 100005;
    
    struct Fhq{
    	int l, r, rand, sz, sum, val, preMin, preMax, sufMin, sufMax, rep, swap, inv;
    } t[N * 2];
    
    int n, q, rt, a, b, idx;
    char s[N], opt[10], c[2];
    
    void inline replace(int p, int v) {
    	t[p].rep = v, t[p].swap = t[p].inv = 0, t[p].val = v, t[p].sum = v * t[p].sz;
    	if (v == 1) t[p].preMin = t[p].sufMin = 0, t[p].preMax = t[p].sufMax = v * t[p].sz;
    	else t[p].preMin = t[p].sufMin = v * t[p].sz, t[p].preMax = t[p].sufMax = 0;
    }
    
    void inline swap(int p) {
    	t[p].swap ^= 1;
    	swap(t[p].l, t[p].r), swap(t[p].preMin, t[p].sufMin);
    	swap(t[p].preMax, t[p].sufMax);
    }
    
    void inline invert(int p) {
    	t[p].inv ^= 1, t[p].sum *= -1, t[p].val *= -1;
    	swap(t[p].preMin, t[p].preMax);
    	t[p].preMin *= -1, t[p].preMax *= -1;
    	swap(t[p].sufMin, t[p].sufMax);
    	t[p].sufMin *= -1, t[p].sufMax *= -1;
    }
    
    void inline pushdown(int p) {
    	if (t[p].rep) {
    		if (ls) replace(ls, t[p].rep);
    		if (rs) replace(rs, t[p].rep);
    		t[p].rep = 0;
    	}
    	if (t[p].swap) {
    		if (ls) swap(ls);
    		if (rs) swap(rs);
    		t[p].swap = 0;
    	}
    	if (t[p].inv) {
    		if (ls) invert(ls);
    		if (rs) invert(rs);
    		t[p].inv = 0;
    	}
    }
    
    void inline pushup(int p) {
    	t[p].sz = t[ls].sz + t[rs].sz + 1;
    	t[p].sum = t[ls].sum + t[rs].sum + t[p].val;
    	t[p].preMin = min(t[ls].preMin, t[ls].sum + t[p].val + t[rs].preMin);
    	t[p].preMax = max(t[ls].preMax, t[ls].sum + t[p].val + t[rs].preMax);
    	t[p].sufMin = min(t[rs].sufMin, t[rs].sum + t[p].val + t[ls].sufMin);
    	t[p].sufMax = max(t[rs].sufMax, t[rs].sum + t[p].val + t[ls].sufMax);
    }
    
    int inline addNode(int v) {
    	t[++idx] = (Fhq) { 0, 0, rand(), 1, v, v, min(v, 0), max(v, 0), min(v, 0), max(v, 0), 0, 0, 0 };
    	return idx;
    }
    
    int build(int l, int r) {
    	if (l > r) return 0;
    	if (l == r) return addNode(s[r] == '(' ? -1 : 1);
    	int mid = (l + r) >> 1, p = addNode(s[mid] == '(' ? -1 : 1);
    	t[p].l = build(l, mid - 1);
    	t[p].r = build(mid + 1, r);
    	pushup(p); 
    	return p;
    }
    // val(a) < val(b)
    int merge(int A, int B) {
    	if (!A || !B) return A + B;
    	if (t[A].rand < t[B].rand) {
    		pushdown(A);
    		t[A].r = merge(t[A].r, B);
    		pushup(A); return A;
    	} else {
    		pushdown(B);
    		t[B].l = merge(A, t[B].l);
    		pushup(B); return B;
    	}
    }
    
    void split(int p, int k, int &x, int &y) {
    	if (!p) { x = y = 0; return; }
    	pushdown(p);
    	if (t[ls].sz + 1 <= k) x = p, split(t[p].r, k - t[ls].sz - 1, t[p].r, y);
    	else y = p, split(t[p].l, k, x, t[p].l);
    	pushup(p);
    }
    
    int main() {
    	srand(time(0));
    	int X, Y, Z;
    	scanf("%d%d%s", &n, &q, s + 1);
    	rt = build(1, n);
    	while (q--) {
    		scanf("%s%d%d", opt, &a, &b);
    		split(rt, a - 1, X, Y);
    		split(Y, b - a + 1, Y, Z);
    		if (*opt == 'R') {
    			scanf("%s", c);
    			replace(Y, *c == '(' ? -1 : 1);
    		} else if (*opt == 'Q') printf("%d
    ", (t[Y].preMax + 1) / 2 + (-t[Y].sufMin + 1) / 2);
    		else if (*opt == 'S') swap(Y);
    		else if (*opt == 'I') invert(Y);
    		rt = merge(X, merge(Y, Z));
    	}
    	return 0;
    }
    

    [CQOI2011]动态逆序对

    (t_i) 表示 (i) 被删除的时间,如果没被删除就是无穷

    统计删除 (i) 去掉的逆序对数:满足 (t_i < t_j, i > j, a_i < a_j)(t_i < t_j, i < j, a_i > a_j)(j) 的个数。离线三维偏序 CDQ 就行了。

    代码懒得写。

    [JLOI2011]不等式组

    转化成讨论 (a) 的正负性,转化成 (a > k)(a < k) 的形式,树状数组就行了。

    #include <iostream>
    #include <cstdio>
    #include <cmath>
    using namespace std;
    
    typedef long long LL;
    
    const int N = 100005, S = 2000005, L = 1e6 + 1;
    
    int n, cnt, tot, c[2][S];
    
    int pos[N], op[N];
    
    char opt[6];
    
    void inline add(int x, int k, int o) {
    	for (; x < S; x += x & -x) c[o][x] += k;
    }
    
    int inline ask(int x, int o) {
    	int res = 0;
    	for (; x; x -= x & -x) res += c[o][x];
    	return res;
    }
    
    int main() {
    	scanf("%d", &n);
    	while (n--) {
    		scanf("%s", opt);
    		if (opt[0] == 'A') {
    			int a, b, c; scanf("%d%d%d", &a, &b, &c);
    			++tot;
    			if (a == 0) cnt += (b > c), op[tot] = -1, pos[tot] = (b > c);
    			else if (a > 0) {
    				LL x = max(-1000000, (int)(floor((double)(c - b) / a) + 1));
    				if (x > 1000000) op[tot] = 2;
    				else add(pos[tot] = x + L, 1, op[tot] = 0); 
    			} else if (a < 0) {
    				LL x = min(1000000, (int)(ceil((double)(c - b) / a) - 1));
    				if (x < -1000000) op[tot] = 2;
    				else add(pos[tot] = x + L, 1, op[tot] = 1);
    			}
    		} else if (opt[0] == 'D') {
    			int i; scanf("%d", &i);
     			if (op[i] == 2) continue;
     			else if (op[i] == -1) cnt -= pos[i], pos[i] = 0;
     			else add(pos[i], -1, op[i]), op[i] = 2;
    		} else {
    			int k; scanf("%d", &k);
    			printf("%d
    ", ask(k + L, 0) + ask(S - 1, 1) - ask(k - 1 + L, 1) + cnt);
    		}
    	}
    	return 0;
    }
    

    [Ynoi2010]y-fast trie

    先把所有数 (mod C)

    然后分类讨论:

    • (x + y ge C),则对应数值为 (x + y - C)
    • (x + y < C),则对应为 (x + y)

    第一类直接选出当前集合的最大值和次大值即可。

    第二类,设 (find(x))(x) 数在当前集合中满足 (x + y < C)(y) 尽量大的 (y),即 (le C - x - 1) 的集合中数的最大值,即 (x) 的最优匹配。

    单项每次删除插入要维护很久,我们发现,如果设 (find(x) = y, find(y) = z),如果 (x < z),那么 (x + y < z + y),那么前者这个数对就肯定不是最优的了,不考虑,所以我们发现最优的肯定是双向匹配,我们不妨维护两个集合 (a, b)(a) 是当前集合中所有的数,(b) 包含双向匹配的数值。

    插入:看 (x + y) 如果是双向匹配加入,如果原先 (y, z) 是最优的就删除 (y + z)

    删除同理。集合可以用 ( ext{multiset})

    #include <iostream>
    #include <cstdio>
    #include <set>
    
    using namespace std;
    
    typedef multiset<int>::iterator MIT;
    
    int n, C, last, sz;
    
    multiset<int> a, b;
    
    int inline find(int x, int op) {
    	if (x == -1) return -1;
    	MIT it = a.lower_bound(C - x);
    	if (it == a.begin()) return -1; --it;
    	if (op == 1 && *it == x && a.count(x) == 1) return it == a.begin() ? -1 : *--it;
    	else return *it;
    }
    
    void inline insert(int x) {
    	++sz; 
    	int y = find(x, 0), z = find(y, 1), w = find(z, 1);
    	if (y != -1 && x > z) {
    		if (z != -1 && w == y) b.erase(b.find(y + z));
    		b.insert(x + y);
    	}
    	a.insert(x);
    }
    
    void inline del(int x) {
    	--sz, a.erase(a.find(x)); 
    	if (!sz) return;
    	int y = find(x, 0), z = find(y, 1), w = find(z, 1);
    	if (y != -1 && x > z) {
    		if (z != -1 && w == y) b.insert(y + z);
    		b.erase(b.find(x + y));
    	} 
    }
    
    int inline query() {
    	int res; MIT it = a.end(); 
    	--it; 
    	if (a.count(*it) > 1) res = (2 * (*it)) % C;
    	else {
    		res = *it; (res += *--it) %= C;
    	}
    	return max(res, b.empty() ? 0 : *--b.end());
    }
    
    int main() {
    	scanf("%d%d", &n, &C);
    	while (n--) {
    		int opt, x; scanf("%d%d", &opt, &x); x ^= last;
    		if (opt == 1) insert(x % C);
    		else del(x % C);
    		if (sz < 2) puts("EE"), last = 0;
    		else printf("%d
    ", last = query());
    	}
    	return 0;
    }
    

    [ZJOI2013]K大数查询

    整体二分 + 支持区间加区间求和的树状数组

    #include <iostream>
    #include <cstdio>
    
    using namespace std;
    
    typedef long long LL;
    
    const int N = 50005;
    
    int n, m, ans[N];
    
    bool vis[N];
    
    struct Q{
    	int op, l, r, id;
    	LL c;
    } q[N], a[N], b[N];
    
    
    struct BIT {
    	LL c[2][N];
    	void inline add(int x, LL k, int o) {
    		for (; x <= n; x += x & -x) c[o][x] += k;
    	}
    	LL inline ask(int x, int o) {
    		LL res = 0;
    		for (; x; x -= x & -x) res += c[o][x];
    		return res; 
    	}
    	// 区间 + k;
    	void change(int l, int r, int k) {
    		add(l, k, 0), add(l, (LL)k * l, 1);
    		add(r + 1, -k, 0), add(r + 1, -(LL)k * (r + 1), 1);
    	}
    	// 前缀和
    	LL query(int x) {
    		return ask(x, 0) * (x + 1) - ask(x, 1);
    	}
    } t;
    
    void solve(int L, int R, int l, int r) {
    	if (L == R) {
    		for (int i = l; i <= r; i++) 
    			if (q[i].op == 2) ans[q[i].id] = R;
    		return;
    	}
    	int mid = (L + R) >> 1, lt = 0, rt = 0;
    	for (int i = l; i <= r; i++) {
    		if (q[i].op == 1) {
    			if (q[i].c <= mid) a[++lt] = q[i];
    			else b[++rt] = q[i], t.change(q[i].l, q[i].r, 1);
    		} else {
    			LL cnt = t.query(q[i].r) - t.query(q[i].l - 1);
    			if (cnt < q[i].c) q[i].c -= cnt, a[++lt] = q[i]; 
    			else b[++rt] = q[i];
    		}
    	}
    	for (int i = l; i <= r; i++)
    		if (q[i].op == 1 && q[i].c > mid) t.change(q[i].l, q[i].r, -1);
    	for (int i = 1; i <= lt; i++) q[l + i - 1] = a[i];
    	for (int i = 1; i <= rt; i++) q[l + lt + i - 1] = b[i];
    	solve(L, mid, l, l + lt - 1); solve(mid + 1, R, l + lt, r);
    }	
    
    
    int main() {
    	scanf("%d%d", &n, &m);
    	for (int i = 1; i <= m; i++) {
    		scanf("%d%d%d%lld", &q[i].op, &q[i].l, &q[i].r, &q[i].c);
    		q[i].id = i;
    		if (q[i].op == 2) vis[i] = true; 
    	}
    	solve(-n, n, 1, m);
    	for (int i = 1; i <= m; i++)
    		if (vis[i]) printf("%d
    ", ans[i]);
    	return 0;
    }
    

    [HNOI2015]接水果

    发现如果考虑对于一个水果能接到它的盘子,这是一条链的包含关系不好做,但是考虑一个盘子能接到的水果,就是子树关系了,可以用 (dfs) 序表示成区间关系。

    (L_u = dfn_u, R_u = dfn_u + size_u - 1),即 ([L_u, R_u])(u) 子树的 (dfs) 序。

    先不妨假设 (dfn_a < dfn_b, dfn_u < dfn_v),设 (a, b)( ext{LCA})(p)

    • (p = a),设 (a, b) 这条链 (a) 下面一个点是 (x),那么一个点需在 (x) 子树外(([1, L_x - 1])([R_x + 1, n])),一个需在 (b) 子树内(([L_b, R_b])),由于我们事先定好了 (dfn_u < dfn_v) 故两种情况,且一定满足 (L_x le L_b)
      • (1 le dfn_u le L_x - 1)(L_b le dfn_v le R_b)
      • (L_b le dfn_u le R_b)(R_x + 1 le dfn_v le n)
    • (p ot= a),那么 (a, b) 即分属两棵子树:
      • (L_a le dfn_u le R_a)(L_b le dfn_v le R_b)

    这样问题变成了矩阵加,单点求第 (k) 小。

    差分 + 整体二分转化为单点加,矩阵求和。

    整体二分 + 扫描线树状数组即可。

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #define rint register int
    using namespace std;
    
    const int N = 40005, S = 16;
    
    int n, p, Q, dfn[N], sz[N], tot, ans[N], d[N], len;
    int L[N], R[N], dfncnt, fa[N][S], dep[N], c[N]; 
    int head[N], numE = 0;
    struct E{
    	int next, v;
    } e[N << 1];
    
    void inline addEdge(int u, int v) {
    	e[++numE] = (E) { head[u], v };
    	head[u] = numE;
    }
    
    struct Opt{
    	int x, y, z, c, id;
    	bool operator < (const Opt &b) const {
    		if (x == b.x) return id < b.id;
    		return x < b.x;
    	}
    } q[9 * N], a[9 * N], b[9 * N];
    
    void inline addQ(int x1, int x2, int y1, int y2, int c) {
    	q[++tot] = (Opt) { x1, y1, 1, c, 0 };
    	q[++tot] = (Opt) { x1, y2 + 1, -1, c, 0 };
    	q[++tot] = (Opt) { x2 + 1, y1, -1, c, 0 };
    	q[++tot] = (Opt) { x2 + 1, y2 + 1, 1, c, 0 };
    }
    
    void dfs(int u) {
    	sz[u] = 1, dfn[u] = ++dfncnt;
    	for (int i = 1; i < S && fa[u][i - 1]; i++)
    		fa[u][i] = fa[fa[u][i - 1]][i - 1];
    	for (int i = head[u]; i; i = e[i].next) {
    		int v = e[i].v;
    		if (v == fa[u][0]) continue;
    		dep[v] = dep[u] + 1, fa[v][0] = u;
    		dfs(v);
    		sz[u] += sz[v];
    	}
    	L[u] = dfn[u], R[u] = dfn[u] + sz[u] - 1;
    }
    
    int inline lca(int x, int y, int &z) {
    	if (dep[x] < dep[y]) swap(x, y);
    	for (int i = S - 1; ~i; i--)
    		if (dep[x] - (1 << i) > dep[y]) x = fa[x][i];
    	z = x; x = fa[x][0];
    	if (x == y) return x;
    	for (int i = S - 1; ~i; i--)
    		if (fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
    	return fa[x][0];
    }
    
    void inline add(int x, int k) {
    	for (; x <= n; x += x & -x) c[x] += k;
    }
    
    void inline clear(int x) {
    	for (; x <= n; x += x & -x) c[x] = 0;
    }
    
    int inline ask(int x) {
    	int res = 0;
    	for (; x; x -= x & -x) res += c[x];
    	return res;
    }
    
    void solve(int L, int R, int l, int r) {
    	if (l > r) return;
    	if (L == R) {
    		for (rint i = l; i <= r; i++)
    			if (q[i].id) ans[q[i].id] = d[R];
    		return;
    	}
    	int mid = (L + R) >> 1, lt = 0, rt = 0;
    	for (rint i = l; i <= r; i++) {
    		if (!q[i].id) {
    			if (q[i].c <= d[mid]) add(q[i].y, q[i].z), a[++lt] = q[i];
    			else b[++rt] = q[i];
    		} else {
    			int cnt = ask(q[i].y);
    			if (q[i].z <= cnt) a[++lt] = q[i]; 
    			else q[i].z -= cnt, b[++rt] = q[i];
    		}
    	}
    	for (rint i = l; i <= r; i++)
    		if (!q[i].id && q[i].c <= d[mid]) clear(q[i].y);
    	for (rint i = 1; i <= lt; i++) q[l + i - 1] = a[i];
    	for (rint i = 1; i <= rt; i++) q[l + lt + i - 1] = b[i];
    	solve(L, mid, l, l + lt - 1); solve(mid + 1, R, l + lt, r);
    }
    
    int main() {
    	scanf("%d%d%d", &n, &p, &Q);
    	for (int i = 1, u, v; i < n; i++)
    		scanf("%d%d", &u, &v), addEdge(u, v), addEdge(v, u);
    	dep[1] = 1, dfs(1);
    	for (int i = 1, a, b, c; i <= p; i++) {
    		scanf("%d%d%d", &a, &b, &c); int x;
    		d[++len] = c;
    		if (dfn[a] > dfn[b]) swap(a, b);
    		int p = lca(a, b, x);
    		if (p == a) {
    			addQ(1, L[x] - 1, L[b], R[b], c);
    			addQ(L[b], R[b], R[x] + 1, n, c);
    		} else addQ(L[a], R[a], L[b], R[b], c);
    	}
    	sort(d + 1, d + 1 + len);
    	len = unique(d + 1, d + 1 + len) - d - 1;
    	for (int i = 1, u, v, k; i <= Q; i++) {
    		scanf("%d%d%d", &u, &v, &k);
    		if (dfn[u] > dfn[v]) swap(u, v);
    		q[++tot] = (Opt) { dfn[u], dfn[v], k, -1, i };
    	}
    	sort(q + 1, q + 1 + tot); solve(1, len, 1, tot);
    	for (int i = 1; i <= Q; i++) printf("%d
    ", ans[i]);
    	return 0;
    }
    

    Rmq Problem / mex

    (pre_i) 表示 (i) 的前驱($a_i = a_{pre_i}, pre_i <i $ 的最大的 (pre_i) )。

    查询就是查 ([1, r])(pre_i < l) 的最小的 (i)

    离线线段树扫一遍就行了。

    #include <cstdio>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    
    const int N = 200005;
    
    int n, m, last, a[N], ans[N];
    
    int dat[N << 2];
    
    struct Q{
    	int l, r, id;
    	bool operator < (const Q &b) const {
    		return r < b.r;
    	}
    } q[N];
    
    void inline pushup(int p) {
    	dat[p] = min(dat[p << 1], dat[p << 1 | 1]);
    }
    
    void change(int p, int l, int r, int x, int k) {
    	if (l == r) { dat[p] = k; return; }
    	int mid = (l + r) >> 1;
    	if (x <= mid) change(p << 1, l, mid, x, k);
    	else change(p << 1 | 1, mid + 1, r, x, k);
    	pushup(p);
    }
    
    int query(int p, int l, int r, int k) {
    	if (l == r) return r;
    	int mid = (l + r) >> 1;
    	if (dat[p << 1] < k) return query(p << 1, l, mid, k);
    	else return query(p << 1 | 1, mid + 1, r, k);
    }
    
    int main() {
    	scanf("%d%d", &n, &m);
    	for (int i = 1; i <= n; i++) scanf("%d", a + i);
    	for (int i = 1; i <= m; i++) scanf("%d%d", &q[i].l, &q[i].r), q[i].id = i;
    	sort(q + 1, q + 1 + m);
    	for (int i = 1, j = 1; i <= n; i++) {
    		if (a[i] <= n + 1) {
    			change(1, 0, n + 1, a[i], i);
    		}
    		while (j <= m && q[j].r == i) {
    			ans[q[j].id] = query(1, 0, n + 1, q[j].l);
    			++j;
    		}
    	}
    	for (int i = 1; i <= m; i++) printf("%d
    ", ans[i]);
    	return 0;
    }
    

    BZOJ #4771. 七彩树

    维护两颗线段树 (A, B)

    • A 以颜色为下标,深度为值
    • B 以深度为下标,数量为值

    A 做线段树合并,合并到叶子的时候把重复的在 B 里面删掉,查的时候在 B 里面查,因为强制在线所以把 B 可持久化一下就行了。

    代码懒得写。

    [Ynoi2017]由乃的OJ

    维护“真值表”(每一段线段树,从左到右/从右到左经过一遍的值),这样拆位是 (O(n + qk log ^2n)) 的,只能得 50 分。

    考虑压在一起做,用二进制运算表示逻辑过程。

    (a_0, a_1, b_0, b_1) 为需要合并的 (a, b) 两个真值表,(c_0, c_1) 是合并后的:

    • (c_0 = (a_0 ext{ and } b_1) ext{ or } (!a_0 ext{ and } b_0))
    • (c_1 = (a_1 ext{ and } b_1) ext{ or } (!a_1 ext{ and } b_0))

    这样就可以 (O(n + q (k + log ^2n))) 了,能得 100 分。

    代码懒得写。

    [SCOI2014]方伯伯的OJ

    类似 NOIP2017 列队的做法。

    开可持久化线段树,下标的绝对下标,信息有人数和编号。另外开个 ( ext{map}) 记录编号位置变过的人的位置。

    4 操作直接在线段树上二分。

    这样就行了。 (O(m log n))

    代码懒得写。

    咕咕咕,之后再写。。

  • 相关阅读:
    使用三星720n液晶显示器的体会
    昨天终于买显示器了
    2005525早上
    抵制日货的结果zt
    读写配置文件类
    递归 访问树节点
    IE条件注释
    闭包 页面渐变
    模块 替换HTML 字符实体(双引号、左右尖括号)
    闭包 查找节点序号
  • 原文地址:https://www.cnblogs.com/dmoransky/p/13760530.html
Copyright © 2011-2022 走看看