主席树学习笔记

主席树学习笔记


前言

主席树,又叫函数式线段树,可持久化线段树,是一种非常有用的数据结构,我们通过一些例题来深入理解主席树的应用。


1. poj2104 K_th Number

Description

You are working for Macrohard company in data structures department. After failing your previous task about key insertion you were asked to write a new data structure that would be able to return quickly k-th order statistics in the array segment.
That is, given an array a[1...n] of different integer numbers, your program must answer a series of questions Q(i, j, k) in the form: "What would be the k-th number in a[i...j] segment, if this segment was sorted?"
For example, consider the array a = (1, 5, 2, 6, 3, 7, 4). Let the question be Q(2, 5, 3). The segment a[2...5] is (5, 2, 6, 3). If we sort this segment, we get (2, 3, 5, 6), the third number is 5, and therefore the answer to the question is 5.

Input

The first line of the input file contains n --- the size of the array, and m --- the number of questions to answer (1 <= n <= 100 000, 1 <= m <= 5 000).
The second line contains n different integer numbers not exceeding 109 by their absolute values --- the array for which the answers should be given.
The following m lines contain question descriptions, each description consists of three numbers: i, j, and k (1 <= i <= j <= n, 1 <= k <= j - i + 1) and represents the question Q(i, j, k).

Output

For each question output the answer to it --- the k-th number in sorted a[i...j] segment.

Sample Input

7 3
1 5 2 6 3 7 4
2 5 3
4 4 1
1 7 3

Sample Output

5
6
3

Hint

This problem has huge input,so please use c-style input(scanf,printf),or you may got time limit exceed.

Solution

这题算是一道主席树的入门题了,静态区间第k大。
首先,如果我们知道一段区间的权值线段树,那么我们可以在线段树上二分来快速得到答案。所以问题转化为如何快速找到所查询区间的权值线段树。
我们考虑对区间进行差分,每个点建立一棵线段树,表示从1到该点的线段树,那么查询 l 到 r 这段区间就相当于用 r 点的线段树减去 l - 1 这个点的线段树。但是如果对每个点都新建一棵线段树是不行的,时间空间都承受不了。所以我们考虑某些优化。
可以看出,对于相邻两个点来说它们之间的差异只有一个值,那么在线段树上就只是一条链的区别。所以我们每次只用新建一条链就行了,这就是主席树。对于后面的点,我们新建一条从根节点到页节点的链,其他部分我们直接指向前一棵树即可。

Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

const int maxn = 100000 + 5;
int n,m;
int cnt;
int a[maxn];
int rt[maxn];
vector<int> v;

inline int getid(int x){return lower_bound(v.begin(),v.end(),x) - v.begin() + 1;}

struct node {
	int sum;
	int ls,rs;
} nod[maxn * 40];

void update(int l,int r,int &x,int y,int pos){
	nod[++cnt] = nod[y];nod[cnt].sum++;x = cnt;
	if(l == r)return;
	int mid = (l + r) >> 1;
	if(pos <= mid)update(l,mid,nod[x].ls,nod[y].ls,pos);
	else update(mid+1,r,nod[x].rs,nod[y].rs,pos);
}

int query(int l,int r,int x,int y,int k){
	if(l == r)return l;
	int mid = (l + r) >> 1;
	int xx = nod[nod[y].ls].sum - nod[nod[x].ls].sum;
	if(xx >= k)return query(l,mid,nod[x].ls,nod[y].ls,k);
	else return query(mid+1,r,nod[x].rs,nod[y].rs,k - xx);	
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i = 1;i <= n;i++)scanf("%d",a+i),v.push_back(a[i]);
	sort(v.begin(), v.end()),v.erase(unique(v.begin(),v.end()),v.end());
	for(int i = 1;i <= n;i++)
		update(1,n,rt[i],rt[i-1],getid(a[i]));
	for(int i = 1;i <= m;i++){
		int a,b,c;scanf("%d%d%d",&a,&b,&c);
		printf("%d\n",v[query(1,n,rt[a-1],rt[b],c) - 1]);
	}
	
	return 0;
} 

2. bzoj3653 谈笑风生

Description

设T 为一棵有根树,我们做如下的定义:
• 设a和b为T 中的两个不同节点。如果a是b的祖先,那么称“a比b不知道
高明到哪里去了”。
• 设a 和 b 为 T 中的两个不同节点。如果 a 与 b 在树上的距离不超过某个给定
常数x,那么称“a 与b 谈笑风生”。
给定一棵n个节点的有根树T,节点的编号为1 到 n,根节点为1号节点。你需
要回答q 个询问,询问给定两个整数p和k,问有多少个有序三元组(a;b;c)满足:

  1. a、b和 c为 T 中三个不同的点,且 a为p 号节点;
  2. a和b 都比 c不知道高明到哪里去了;
  3. a和b 谈笑风生。这里谈笑风生中的常数为给定的 k。

Input

输入文件的第一行含有两个正整数n和q,分别代表有根树的点数与询问的个数。接下来n - 1行,每行描述一条树上的边。每行含有两个整数u和v,代表在节点u和v之间有一条边。
接下来q行,每行描述一个操作。第i行含有两个整数,分别表示第i个询问的p和k。

Output

输出 q 行,每行对应一个询问,代表询问的答案。

Sample Input

5 3
1 2
1 3
2 4
4 5
2 2
4 1
2 3

Sample Output

3
1
3

Hint

1<=P<=N
1<=K<=N
N<=300000
Q<=300000

Solution

我们可以发现,a 和 b 必定有祖孙关系,并且如果 a 和 b 都已经确定,那么对答案的贡献就是深度较大的那个点的子树size。
然后我们分情况讨论b。

  1. 如果 b 在 a 上方,就是 a 的子树size。所以总共对答案的贡献为min(dep[a], k) * size[a];
  2. 如果 b 在 a 下方,那么答案就是所有在 a 的子树内的点x,并且 dep[x] - dep[a] <= k 的子树size和(语死早。然后维护子树就想到用dfs序 + 主席树来搞一搞就好啦。
    主席树中以dep为下标,存的是从第一个点到当前点每一个dep对答案的贡献。查询时就是一棵子树的out减去in。然后就完啦。

Code

#include <cstdio>
#include <iostream>
using namespace std;
#define adde(u,v) (e[cnt] = (edge){v,head[u]}, head[u] = &e[cnt++])
typedef long long LL;

const int maxn = 3e5 + 10;
int n, m, cnt, pcnt, num, M;
int dep[maxn], son[maxn], lp[maxn], rt[maxn], rp[maxn];

struct edge {
	int v;
	edge *next;
} e[maxn << 1], *head[maxn];

struct Seg {
	int l, r;
	LL sum;
} nod[maxn * 50];

void dfs(int u, int fa) {
	dep[u] = dep[fa] + 1, M = max(M, dep[u]);
	for (edge *k = head[u]; k; k = k->next) if (k->v != fa) dfs(k->v, u), son[u] += son[k->v] + 1;
}

void insert(int &x, int y, int l, int r, int p, LL ad) {
	x = ++num, nod[x] = nod[y], nod[x].sum += ad;
	if(l == r) return;
	int mid = (l + r) >> 1;
	if(p <= mid) insert(nod[x].l, nod[y].l, l, mid, p, ad);
	else insert(nod[x].r, nod[y].r, mid + 1, r, p, ad);
}

void build(int u, int fa) {
	lp[u] = ++pcnt;
	insert(rt[pcnt], rt[pcnt - 1], 1, M, dep[u], son[u]);
	for (edge *k = head[u]; k; k = k->next) if (k->v != fa) build(k->v, u);
	rp[u] = pcnt;
}

LL query(int x, int l, int r, int L, int R) {
	if(!nod[x].sum) return 0;
	if(l == L && r == R) return nod[x].sum;
	int mid = (l + r) >> 1;
	if(R <= mid) return query(nod[x].l, l, mid, L, R);
	if(L > mid) return query(nod[x].r, mid + 1, r, L, R);
	return query(nod[x].l, l, mid, L, mid) + query(nod[x].r, mid + 1, r, mid + 1, R);
}

int main() {
	int a, b;
	scanf("%d%d", &n, &m);
	for (int i = 1; i < n; i++)
	scanf("%d%d", &a, &b), adde(a, b), adde(b, a);
	dfs(1, 0);
	M = n;
	build(1, 0);
	for (int i = 0; i < m; i++) {
		int p, k;
		scanf("%d%d", &p, &k);
		LL ans = min(k, dep[p] - 1) * son[p];
		ans -= query(rt[lp[p]], 1, M, min(dep[p] + 1, M), min(dep[p] + k, M));
		ans += query(rt[rp[p]], 1, M, min(dep[p] + 1, M), min(dep[p] + k, M));
		printf("%lld\n", ans);
	}
	return 0;
}

3. bzoj4571 (SCOI2016)美味

Description

一家餐厅有 n 道菜,编号 1...n ,大家对第 i 道菜的评价值为 ai(1≤i≤n)。有 m 位顾客,第 i 位顾客的期
望值为 bi,而他的偏好值为 xi 。因此,第 i 位顾客认为第 j 道菜的美味度为 bi XOR (aj+xi),XOR 表示异或
运算。第 i 位顾客希望从这些菜中挑出他认为最美味的菜,即美味值最大的菜,但由于价格等因素,他只能从第
li 道到第 ri 道中选择。请你帮助他们找出最美味的菜。

Input

第1行,两个整数,n,m,表示菜品数和顾客数。
第2行,n个整数,a1,a2,...,an,表示每道菜的评价值。
第3至m+2行,每行4个整数,b,x,l,r,表示该位顾客的期望值,偏好值,和可以选择菜品区间。
1≤n≤2×105,0≤ai,bi,xi<105,1≤li≤ri≤n(1≤i≤m);1≤m≤10^5

Output

输出 m 行,每行 1 个整数,ymax ,表示该位顾客选择的最美味的菜的美味值。

Sample Input

4 4
1 2 3 4
1 4 1 4
2 3 2 3
3 2 3 3
4 1 2 4

Sample Output

9
7
6
7

Solution

很明显,我们可以用贪心的思想,从高位一直求到低位。然后我们就可以发现,每次确定一位其实确定的是一个区间。于是我们可以用线段树来维护,查询某一位是否有能满足的数。对于题目中询问的+x,我们可以在查询时-x。

Code

#include <cstdio>
#include <iostream>
using namespace std;

const int maxn = 1e5 + 5, maxm = 1e6;
int n, m;
int num, a[maxn], s[30], rt[maxn];

struct Seg {
	int sum, l, r;
} nod[maxn * 30];

void insert(int &x, int y, int l, int r, int ad) {
	x = ++num, nod[x] = nod[y], nod[x].sum++;
	if(l == r) return;
	int mid = (l + r) >> 1;
	if(ad <= mid) insert(nod[x].l, nod[y].l, l, mid, ad);
	else insert(nod[x].r, nod[y].r, mid + 1, r, ad);
}

bool query(int x, int y, int l, int r, int L, int R) {
	if(nod[y].sum - nod[x].sum == 0) return false;
	if(L <= l && R >= r) return true;
	int mid = (l + r) >> 1, ret = 0;
	if (L <= mid) ret |= query(nod[x].l, nod[y].l, l, mid, L, R);
	if (R >  mid) ret |= query(nod[x].r, nod[y].r, mid + 1, r, L, R);
	return ret;
}

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++) {
		scanf("%d", a + i);
		insert(rt[i], rt[i - 1], 0, maxm, a[i]);
	}
	for (int i = 0; i < m; i++) {
		int b, x, l, r;
		scanf("%d%d%d%d", &b, &x, &l, &r);
		//for (int j = 0, t = b; j < 20; j++, t >>= 1) s[i] = t & 1;
		int L = 0, R = (1 << 19) - 1;
		for (int j = 18; ~j; j--) {
		    int mid = (L + R) >> 1;
		    if (b>>j&1){
				if (query(rt[l - 1], rt[r],0, maxm, L - x, mid - x)) R = mid;
				else L = mid + 1;
			}
			else {
				if(query(rt[l - 1], rt[r], 0, maxm, mid + 1 - x, R - x)) L = mid + 1;
				else R = mid;
			}
		}
		printf("%d\n", L ^ b);
	}
	return 0;
}

4. bzoj1901 Dynamic Rankings

Description

给定一个含有n个数的序列a[1],a[2],a[3]……a[n],程序必须回答这样的询问:对于给定的i,j,k,在a[i],a[i+1],a[i+2]……a[j]中第k小的数是多少(1≤k≤j-i+1),并且,你可以改变一些a[i]的值,改变后,程序还能针对改变后的a继续回答上面的问题。你需要编一个这样的程序,从输入文件中读入序列a,然后读入一系列的指令,包括询问指令和修改指令。对于每一个询问指令,你必须输出正确的回答。 第一行有两个正整数n(1≤n≤10000),m(1≤m≤10000)。分别表示序列的长度和指令的个数。第二行有n个数,表示a[1],a[2]……a[n],这些数都小于10^9。接下来的m行描述每条指令,每行的格式是下面两种格式中的一种。 Q i j k 或者 C i t Q i j k (i,j,k是数字,1≤i≤j≤n, 1≤k≤j-i+1)表示询问指令,询问a[i],a[i+1]……a[j]中第k小的数。C i t (1≤i≤n,0≤t≤10^9)表示把a[i]改变成为t。

Input

对于每一次询问,你都需要输出他的答案,每一个输出占单独的一行。

Sample Input

5 3
3 2 1 4 7
Q 1 4 3
C 2 6
Q 2 5 3

Sample Output

3
6

Hint

20%的数据中,m,n≤100; 40%的数据中,m,n≤1000; 100%的数据中,m,n≤10000。

Solution

带修改区间第k大,树状数组套线段树裸题。这里用了一个主席树的写法,可以节省很多空间。

Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;

const int maxn = 1e4 + 5, maxm = 1e4 + 5;
int n, m, cnt, num;
vector<int> v, L, R;
int a[maxn + maxm], rt[maxn];

struct Seg {
	int l, r, sum;
} nod[maxn * 200];

struct que {
	int x, y, k, tp;
} q[maxn];

inline int getid(int x) {return lower_bound(v.begin(), v.end(), x) - v.begin() + 1;}

void update(int &u, int l, int r, int x, int ad) {
	if(!u)nod[++num] = nod[u], u = num;
	nod[u].sum += ad;
	if(l == r) return;

	int mid = (l + r) >> 1;
	if(x <= mid) update(nod[u].l, l, mid, x, ad);
	else update(nod[u].r, mid + 1, r, x, ad);
}

int query(int l, int r, int k) {
	if(l == r) return l;
	int mid = (l + r) >> 1;
	int x = 0;
	for (int i = 0, sz = L.size(); i < sz; i++) x -= nod[nod[L[i]].l].sum;
	for (int i = 0, sz = R.size(); i < sz; i++) x += nod[nod[R[i]].l].sum;
	if(x >= k) {
		for (int i = 0, sz = L.size(); i < sz; i++) L[i] = nod[L[i]].l;
		for (int i = 0, sz = R.size(); i < sz; i++) R[i] = nod[R[i]].l;
		return query(l, mid, k);
	}
	else {
		for (int i = 0, sz = L.size(); i < sz; i++) L[i] = nod[L[i]].r;
		for (int i = 0, sz = R.size(); i < sz; i++) R[i] = nod[R[i]].r;
		return query(mid + 1, r, k - x);
	}
}

inline void add(int x, int y, int ad) {for (; x <= n; x += x & -x) update(rt[x], 1, cnt, y, ad);}

inline int ask(int l, int r, int k) {
	L.clear(), R.clear();
	for (int i = l - 1; i > 0; i -= i & -i) L.push_back(rt[i]);
	for (int i = r; i > 0; i -= i & -i) R.push_back(rt[i]);
	return query(1, cnt, k);
}

void build() { for (int i = 1; i <= n; i++) add(i, getid(a[i]), 1);}

int main() {
	int t;
	while(~ scanf("%d%d", &n, &m)) {
		v.clear();
		for (int i = 1; i <= n; i++) scanf("%d", a + i), v.push_back(a[i]);
		for (int i = 1; i <= m; i++) {
			char s[2];
			int op, x, y, k;
			scanf("%s%d%d", s, &x, &y);
			if(s[0] == 'C') q[i] = (que){x, y, 0, 1}, v.push_back(y);
			else scanf("%d", &k), q[i] = (que){x, y, k, 0};
		}
		sort(v.begin(), v.end()), v.erase(unique(v.begin(), v.end()), v.end());
		cnt = v.size();
		num = 0;
		memset(rt, 0, sizeof(rt));
		build();

		for (int i = 1; i <= m; i++) {
			if(q[i].tp == 1) add(q[i].x, getid(a[q[i].x]), -1), add(q[i].x, getid(a[q[i].x] = q[i].y), 1);
			else printf("%d\n", v[ask(q[i].x, q[i].y, q[i].k) - 1]);
		}
	}
	return 0;
}

5. bzoj2588 Count on a tree

Description

给定一棵N个节点的树,每个点有一个权值,对于M个询问(u,v,k),你需要回答u xor lastans和v这两个节点间第K小的点权。其中lastans是上一个询问的答案,初始为0,即第一个询问的u是明文。

Input

第一行两个整数N,M。
第二行有N个整数,其中第i个整数表示点i的权值。
后面N-1行每行两个整数(x,y),表示点x到点y有一条边。
最后M行每行两个整数(u,v,k),表示一组询问。

Output

M行,表示每个询问的答案。

Sample Input

8 5
105 2 9 3 8 5 7 7
1 2
1 3
1 4
3 5
3 6
3 7
4 8
2 5 1
0 5 2
10 5 3
11 5 4
110 8 2

Sample Output

2
8
9
105
7

Hint

N,M<=100000
暴力自重。。。

Solution

题意就不说了,树上一条链的区间第k大。
直接维护链显然是不行的,我们可以转化一下,维护每一个点到根节点的一段区间。那么树上两点的路径就是这两点到根节点的区间相加,再减去两倍lca到根节点的区间,加上lca。
所以维护主席树时就从节点的父节点copy过来,再加上这个节点的值。

Code

#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
#define adde(u,v) (e[cnt] = (edge){v, head[u]}, head[u] = &e[cnt++])

char f;
inline read(int &x) {
	x = 0;f = getchar();
	while(f < '0' || f > '9') f = getchar();
	while(f >= '0' && f <= '9') x = x * 10 + f - '0', f = getchar();
}

const int maxn = 1e5 + 10;
int n, m, M, num, cnt;
int a[maxn], rt[maxn];
vector<int> ve;
int fa[maxn][20], dep[maxn];

struct edge {
	int v; edge *next;
}e[maxn << 1], *head[maxn];

struct Seg {int l, r, sum;} nod[maxn * 50];

inline int getid(int x) {return lower_bound(ve.begin(), ve.end(), x) - ve.begin() + 1;}

void insert(int &x, int y, int l, int r, int ad) {
	nod[x = ++num] = nod[y], nod[x].sum++;
	if(l == r) return;
	int mid = (l + r) >> 1;
	if(ad <= mid) insert(nod[x].l, nod[y].l, l, mid, ad);
	else insert(nod[x].r, nod[y].r, mid + 1, r, ad); 
}

void dfs(int u, int f) {
	dep[u] 	= dep[f] + 1;
	insert(rt[u], rt[f], 1, M, getid(a[u]));
	fa[u][0] = f;
	for(int i = 1; i < 20; i++) fa[u][i] = fa[fa[u][i-1]][i-1];
	for (edge *k = head[u]; k; k = k->next) if(k->v != f) dfs(k->v, u);
}

inline int ask(int u, int v) {
	if(dep[u] < dep[v]) swap(u, v);
	int k = dep[u] - dep[v];
	for(int i = 0; i < 20; i++) if(k & (1 << i)) u = fa[u][i];
	for(int i = 20 - 1; ~i; i--) if(fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i];
	return u == v ? u : fa[u][0];
}

inline int query(int u, int v, int f, int ff, int k) {
	int l = 1, r = M, mid;
	while(l < r) {
		mid = (l + r) >> 1;
		int x = nod[nod[u].l].sum + nod[nod[v].l].sum - nod[nod[f].l].sum - nod[nod[ff].l].sum;
		if(x >= k) u = nod[u].l, v = nod[v].l, f = nod[f].l, ff = nod[ff].l, r = mid;
		else u = nod[u].r, v = nod[v].r, f = nod[f].r, ff = nod[ff].r, k -= x, l = mid + 1;
	}
	return l;
}

int main() {
	read(n), read(m);
	for (int i = 1; i <= n; i++) read(a[i]), ve.push_back(a[i]);
	sort(ve.begin(), ve.end()), ve.erase(unique(ve.begin(), ve.end()), ve.end());
	M = ve.size();
	int u, v;
	for (int i = 1; i < n; i++) read(u), read(v), adde(u,v), adde(v,u);
	dfs(1,0);
	int las = 0;
	int k, p;
	for(int i = 0; i < m; i++) {
		read(u), read(v), read(k), u ^= las;
		p = ask(u, v);
		printf("%d", las = ve[query(rt[u], rt[v], rt[p], rt[fa[p][0]], k) - 1]);
		if(i != m - 1) printf("\n");
	}
	return 0;
}

6. bzoj2653 middle

Description

一个长度为n的序列a,设其排过序之后为b,其中位数定义为b[n/2],其中a,b从0开始标号,除法取下整。
给你一个长度为n的序列s。
回答Q个这样的询问:s的左端点在[a,b]之间,右端点在[c,d]之间的子序列中,最大的中位数。
其中a < b < c < d。
位置也从 0 开始标号。
我会使用一些方式强制你在线。

Input

第一行序列长度n。
接下来n行按顺序给出a中的数。
接下来一行Q。
然后Q行每行a,b,c,d,我们令上个询问的答案是x(如果这是第一个询问则x=0)。
令数组q={(a+x)%n,(b+x)%n,(c+x)%n,(d+x)%n}。
将q从小到大排序之后,令真正的要询问的a=q[0],b=q[1],c=q[2],d=q[3]。
输入保证满足条件。

Output

Q行依次给出询问的答案。

Sample Input

5
170337785
271451044
22430280
969056313
206452321
3
3 1 0 2
2 3 1 4
3 1 4 0

Sample Output

271451044
271451044
969056313

Hint

0:n,Q<=100
1,...,5:n<=2000
0,...,19:n<=20000,Q<=25000

Solution

这道题不能像前面几道题一样维护权值线段树,而是应该建立普通的区间线段树。
求中位数这种题一般都是二分答案然后check,这道题同样。
我们以某个特定的值建立线段树,小于他的位置赋值为-1,大于赋值为1。
check时就可以查询某段区间的值是否大于等于0。
我们可以发现,对于排序后两个相邻的值,它们的区别只有一个地方的值由1变为了-1。所以就可以用主席树来搞一搞啦。

Code

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define ls nod[x].l
#define rs nod[x].r

const int maxn = 2e4 + 5;
int n, m, num;
int rt[maxn], q[5];

struct P {
	int x, id;
	bool operator < (const P &a) const {return x < a.x;}
} aa[maxn];

struct Seg {
	int l, r, sum, lmax, rmax;
} nod[maxn * 50];

void modify(int x) {
	nod[x].lmax = max(nod[ls].lmax, nod[ls].sum + nod[rs].lmax);
	nod[x].rmax = max(nod[rs].rmax, nod[rs].sum + nod[ls].rmax);
}

void build(int &x, int l, int r) {
	nod[x = ++num].sum = (r - l + 1);
	if(l == r) {
		nod[x].lmax = nod[x].rmax = 1;
		return;
	}
	int mid = (l + r) >> 1;
	build(ls, l, mid);
	build(rs, mid + 1, r);
	modify(x);
}

void insert(int &x, int y, int l, int r, int p) {
	nod[x = ++num] = nod[y], nod[x].sum -= 2;
	if(l == r) {
		nod[x].lmax = nod[x].rmax = -1;
		return;
	}
	int mid = (l + r) >> 1;
	if(p <= mid) insert(ls, nod[y].l, l, mid, p);
	else insert(rs, nod[y].r, mid + 1, r, p);
	modify(x);
}

int query(int x, int l, int r, int L, int R) {
	if(L > R) return 0;
	if (l == L && r == R)
		return nod[x].sum;
	int mid = (l + r) >> 1;
	if(R <= mid) return query(ls, l, mid, L, R);
	if(L > mid) return query(rs, mid + 1, r, L, R);
	return query(ls, l, mid, L, mid) + query(rs, mid + 1, r, mid + 1, R);
}

int ql(int x, int l, int r, int L, int R) {
	if(l == L && r == R) return nod[x].lmax;
	int mid = (l + r) >> 1;
	if(R <= mid) return ql(ls, l, mid, L, R);
	if(L > mid) return ql(rs, mid + 1, r, L, R);
	return max(ql(ls, l, mid, L, mid), query(ls, l, mid, L, mid) + ql(rs, mid + 1, r, mid + 1, R));
}

int qr(int x, int l, int r, int L, int R) {
	if(l == L && r == R) return nod[x].rmax;
	int mid = (l + r) >> 1;
	if(R <= mid) return qr(ls, l, mid, L, R);
	if(L > mid) return qr(rs, mid + 1, r, L, R);
	return max(qr(rs, mid + 1, r, mid + 1, R), query(rs, mid + 1, r, mid + 1, R) + qr(ls, l, mid, L, mid));
}

int check(int a, int b, int c, int d) {
	int l = 0, r = n - 1, mid;
	while(l < r) {
		mid = (l + r + 1) >> 1;
		if(query(rt[mid], 0, n - 1, b + 1, c - 1) + qr(rt[mid], 0, n - 1, a, b) + ql(rt[mid], 0, n-1, c, d) >= 0)
			l = mid;
		else r = mid - 1;
	}
	return aa[l].x;
}

int main() {
	scanf("%d", &n);
	for (int i = 0; i < n; i++) scanf("%d", &aa[i].x), aa[i].id = i;
	sort(aa, aa + n);
	build(rt[0], 0, n - 1);
	for (int i = 1; i <= n; i++) insert(rt[i], rt[i - 1], 0, n - 1, aa[i - 1].id);
	scanf("%d", &m);
	int x = 0;
	for (int i = 0; i < m; i++) {
		scanf("%d%d%d%d", q, q + 1, q + 2, q + 3);
		q[0] = (q[0] + x) % n, q[1] = (q[1] + x) % n, q[2] = (q[2] + x) % n, q[3] = (q[3] + x) % n;
		sort(q, q + 4);
		x = check(q[0], q[1], q[2], q[3]);
		printf("%d\n", x);
	}
	return 0;
}

7. bzoj3551 Peaks加强版

Description

在Bytemountains有N座山峰,每座山峰有他的高度h_i。有些山峰之间有双向道路相连,共M条路径,每条路径有一个困难值,这个值越大表示越难走,现在有Q组询问,每组询问询问从点v开始只经过困难值小于等于x的路径所能到达的山峰中第k高的山峰,如果无解输出-1。

Input

第一行三个数N,M,Q。
第二行N个数,第i个数为h_i
接下来M行,每行3个数a b c,表示从a到b有一条困难值为c的双向路径。
接下来Q行,每行三个数v x k,表示一组询问。v=v xor lastans,x=x xor lastans,k=k xor lastans。如果lastans=-1则不变。

Output

对于每组询问,输出一个整数表示答案。

Sample Input

10 11 4
1 2 3 4 5 6 7 8 9 10
1 4 4
2 5 3
9 8 2
7 8 10
7 1 4
6 7 1
6 4 8
2 1 5
10 8 10
3 4 7
3 4 6
1 5 2
1 5 6
1 5 8
8 9 2

Sample Output

6
1
-1
8

Hint

N<=10^5, M,Q<=5*105,h_i,c,x<=109。

Solution

首先,不知道怎么建图的,请移步bzoj3732 Network
好了,现在大家都知道Kruskal重构树及其性质。
我们知道,树上的权值是递增的,所以我们可以倍增往上找到最后一个val小于等于x的点。那么以这个点为根的子树内所有点就是v能到达的所有点。在子树内找第k大,又是dfs序 + 主席树。

Code

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;

const int maxn = 2e5 + 10, maxm = 5e5 + 10; 
int n, m, q, cnt, num, M, tnum;
int in[maxn], out[maxn], fa[maxn], f[maxn][20];
int val[maxn], rt[maxn], ch[maxn][2], a[maxn], vis[maxn];
vector<int> v;

int find(int x) {return fa[x] == x ? x : fa[x] = find(fa[x]);}
inline int getid(int x) {return lower_bound(v.begin(), v.end(), x) - v.begin() + 1;}

struct edge {
	int u, v, w;
	bool operator < (const edge &a) const {return w < a.w;}
} e[maxm];

struct Seg {int l, r, sum;} nod[maxn << 5];

void insert(int &x, int y, int l, int r, int ad) {
	nod[x = ++tnum] = nod[y], nod[x].sum++;
	if(l == r) return;
	int mid = (l + r) >> 1;
	if(ad <= mid) insert(nod[x].l, nod[y].l, l, mid, ad);
	else insert(nod[x].r, nod[y].r, mid+1, r, ad);
}

void dfs(int u) {
	vis[u] = 1;
	in[u] = ++num;
	if(u > n) rt[num] = rt[num-1];
	else insert(rt[num], rt[num-1], 1, M, getid(a[u]));
	if(ch[u][0]) dfs(ch[u][0]), dfs(ch[u][1]);
	out[u] = num;
}

inline int ask(int u, int x) {
	for(int i = 19; ~i; i--) if(val[f[u][i]] && val[f[u][i]] <= x) u = f[u][i];
	return u;
}

int query(int x, int y, int l, int r, int k) {
	if(nod[y].sum - nod[x].sum < k) return -1;
	if(l == r) return v[l - 1];
	int mid = (l + r) >> 1;
	int ad = nod[nod[y].l].sum - nod[nod[x].l].sum;
	if(ad >= k) return query(nod[x].l, nod[y].l, l, mid, k);
	return query(nod[x].r, nod[y].r, mid+1, r, k - ad);
}

int main() {
	scanf("%d%d%d", &n, &m, &q), cnt = n;
	for(int i = 1; i <= n; i++) scanf("%d", a + i), v.push_back(a[i]);
	for(int i = 0; i < m; i++) scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w);
	sort(e, e + m);
	for(int i = 1; i <= 2 * n; i++) fa[i] = i;
	for(int i = 0; i < m; i++) {
		int u = e[i].u, v = e[i].v;
		if(find(u) == find(v)) continue;
		ch[++cnt][0] = fa[u], ch[cnt][1] = fa[v];
		fa[fa[u]] = fa[fa[v]] = f[fa[u]][0] = f[fa[v]][0] = cnt;
		val[cnt] = e[i].w; 
	}
	for(int j = 1; j < 20; j++) 
		for(int i = 1; i <= cnt; i++) f[i][j] = f[f[i][j-1]][j-1];
	sort(v.begin(), v.end()), v.erase(unique(v.begin(), v.end()), v.end());
	M = v.size();
	for(int i = cnt; i; i--) if(!vis[i])dfs(find(i));
	int u, x, k, las = 0;
	for(int i = 0; i < q; i++) {
		scanf("%d%d%d", &u, &x, &k);
		if(las != -1)u ^= las, x ^= las, k ^= las;
		u = ask(u, x);
		int xx = nod[rt[out[u]]].sum - nod[rt[in[u] - 1]].sum;
		if(xx < k) las = -1;
		else las = query(rt[in[u] - 1], rt[out[u]], 1, M, xx - k + 1);
		printf("%d\n", las);
	}
	return 0;
}

小结

经过一系列的题,我们可以摸出一些主席树的套路。一般是权值树,但也有少部分题是区间树。涉及到树上问题时,我们一般可以维护一个dfs序来搞一搞。总之,还是要多做一些题,多熟悉一些套路。

posted @ 2017-01-03 10:44  ZegWe  阅读(452)  评论(0编辑  收藏  举报