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))

    代码懒得写。

    咕咕咕,之后再写。。

  • 相关阅读:
    Android开发 ViewConfiguration View的配置信息类
    Android 开发 倒计时功能 转载
    Android 开发 关于7.0 FileUriExposedException异常 详解
    Android 开发 实现文本搜索功能
    Android 开发 Activity里获取View的宽度和高度 转载
    Android 开发 存储目录的详解
    Android 开发 Fresco框架点击小图显示全屏大图实现 ZoomableDraweeView
    Android 开发 将window变暗
    Android 开发 DisplayMetrics获取Android设备的屏幕高宽与其他信息
    Android 开发 DP、PX、SP转换详解
  • 原文地址:https://www.cnblogs.com/dmoransky/p/13760530.html
Copyright © 2011-2022 走看看