1. 程式人生 > 實用技巧 >學習筆記:高階資料結構【2020落谷省選夏令營】

學習筆記:高階資料結構【2020落谷省選夏令營】

習題

方差

拆式子,維護支援求區間平方和和區間和的線段樹,記一個加法懶標記。

#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\n", 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\n", ((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\n", 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\n", 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\n", 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} \rceil + \lceil \frac{-min(suf)}2 \rceil\)

證明

必要性:每次將右括號改成左括號會讓右邊的 \(pre +2\),因此至少要加 $ \lceil \frac{max(pre)}{2} \rceil$ 才能讓每個合法。字尾同理。

充分性:考慮構造,對於 \(pre\) 來說,把從左往右 $ \lceil \frac{max(pre)}{2} \rceil$ 個右括號改成左括號,對於 \(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\n", (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\n", ask(k + L, 0) + ask(S - 1, 1) - ask(k - 1 + L, 1) + cnt);
		}
	}
	return 0;
}

[Ynoi2010]y-fast trie

先把所有數 \(\bmod 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\)

刪除同理。集合可以用 \(\text{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\n", 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\n", 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\)\(\text{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 \not= 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\n", 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\n", 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 \text{ and } b_1) \text{ or } (!a_0 \text{ and } b_0)\)
  • \(c_1 = (a_1 \text{ and } b_1) \text{ or } (!a_1 \text{ and } b_0)\)

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

程式碼懶得寫。

[SCOI2014]方伯伯的OJ

類似 NOIP2017 列隊的做法。

開可持久化線段樹,下標的絕對下標,資訊有人數和編號。另外開個 \(\text{map}\) 記錄編號位置變過的人的位置。

4 操作直接線上段樹上二分。

這樣就行了。 \(O(m \log n)\)

程式碼懶得寫。

咕咕咕,之後再寫。。