Ds100p -「数据结构百题」1~10

1.「一本通 4.6 例 1」营业额统计

原题来自:HNOI 2002

Tiger 最近被公司升任为营业部经理,他上任后接受公司交给的第一项任务便是统计并分析公司成立以来的营业情况。

Tiger 拿出了公司的账本,账本上记录了公司成立以来每天的营业额。分析营业情况是一项相当复杂的工作。由于节假日,大减价或者是其他情况的时候,营业额会出现一定的波动,当然一定的波动是能够接受的,但是在某些时候营业额突变得很高或是很低,这就证明公司此时的经营状况出现了问题。

经济管理学上定义了一种最小波动值来衡量这种情况:记该天以前某一天的营业额为 (a_i),该天营业额为 (b),则该天的最小波动值 (delta=min |a_i-b|),当最小波动值越大时,就说明营业情况越不稳定。而分析整个公司的从成立到现在营业情况是否稳定,只需要把每一天的最小波动值加起来就可以了。

你的任务就是编写一个程序帮助 Tiger 来计算这一个值,第一天的最小波动值为第一天的营业额。

一句话题意

给出一个 (n) 个数的数列 ({a_n}),对于第 (i) 个元素 (a_i),定义 (f_i=min |a_i-a_j|),其中 (1le jlt i,f_1=a_1)。求 (sum f_i)


没什么可说的,就是板子,可以用来调一下指针版splay。不过我发现指针版跑的飞慢是在搞嘛

还是正经说一下吧

这道题要查询最小波动值。我们只需要把x的前驱和后继求出来,看一下谁跟x的关系更好离的更近,再算出答案,再把这个数insert进去就好了

#include <cstdio>
#include <algorithm>
#define mid (l + r >> 1)
#define int long long

using namespace std;

const int SIZE = (1 << 15) + 5;
const int INF = 0x7fffffff;
struct SplayNode {
    SplayNode *son[2], *fa;
    int val, siz;
    SplayNode(SplayNode *fa = NULL, int val = 0) : fa(fa), val(val) {
        *son = *(son + 1) = NULL;
        siz = 1;
    }
    inline bool islyczbing() { return this == fa->son[1]; }
    inline int rnk() { return 1 + (*son ? (*son)->siz : 0); }
    inline void up() { siz = 1 + (*son ? (*son)->siz : 0) + ((*(son + 1)) ? (*(son + 1))->siz : 0); }
} * root;

inline void rotate(SplayNode *x) {
    bool k = x->islyczbing();
    SplayNode *y = x->fa, *z = y->fa, *w = x->son[!k];
    if (root == y)
        root = x;
    else
        z->son[y->islyczbing()] = x;
    x->fa = z;
    y->fa = x;
    x->son[!k] = y;
    y->son[k] = w;
    if (w)
        w->fa = y;
    y->up();
    x->up();
}

inline void cosplay(SplayNode *x) {
    while (x != root) {
        if (x->fa != root)
            rotate(x->islyczbing() ^ x->fa->islyczbing() ? x : x->fa);
        rotate(x);
    }
}

inline void insert(int val) {
    if (!root)
        return (void)(root = new SplayNode(NULL, val));
    SplayNode *p = root, *fa = NULL;
    while (p) {
        fa = p;
        p = p->son[val > p->val];
    }
    p = new SplayNode(fa, val);
    fa->son[val > fa->val] = p;
    cosplay(p);
}

inline int getpre(int val) {
    SplayNode *p = root, *lst = NULL;
    while (p) {
        if (val > p->val)
            lst = p, p = p->son[1];
        else
            p = p->son[0];
    }
    if (lst)
        return cosplay(lst), lst->val;
    return -INF;
}

inline int getnext(int val) {
    SplayNode *p = root, *lst = NULL;
    while (p) {
        if (val < p->val)
            lst = p, p = p->son[0];
        else
            p = p->son[1];
    }
    if (lst)
        return cosplay(lst), lst->val;
    return INF;
}

signed main() {
    root = NULL;
    int n;
    scanf("%lld", &n);
    int ans = 0;
    for (int i = 1, x; i <= n; ++i) {
        scanf("%lld", &x);
        if (i == 1)
            ans += x;
        else
            ans += min(x - getpre(x + 1), getnext(x - 1) - x);
        insert(x);
    }
    printf("%lld
", ans);
    return 0;
}

2.[SHOI2013]发牌

在一些扑克游戏里,如德州扑克,发牌是有讲究的。一般称呼专业的发牌手为荷官。荷官在发牌前,先要销牌(burn card)。所谓销牌,就是把当前在牌库顶的那一张牌移动到牌库底,它用来防止玩家猜牌而影响游戏。

假设一开始,荷官拿出了一副新牌,这副牌有N 张不同的牌,编号依次为1到N。由于是新牌,所以牌是按照顺序排好的,从牌库顶开始,依次为1, 2,……直到N,N 号牌在牌库底。为了发完所有的牌,荷官会进行N 次发牌操作,在第i 次发牌之前,他会连续进行Ri次销牌操作, Ri由输入给定。请问最后玩家拿到这副牌的顺序是什么样的?

举个例子,假设N = 4,则一开始的时候,牌库中牌的构成顺序为{1, 2, 3, 4}。

假设R1=2,则荷官应该连销两次牌,将1 和2 放入牌库底,再将3 发给玩家。目前牌库中的牌顺序为{4, 1, 2}。

假设R2=0,荷官不需要销牌,直接将4 发给玩家,目前牌库中的牌顺序为{1,2}。

假设R3=3,则荷官依次销去了1, 2, 1,再将2 发给了玩家。目前牌库仅剩下一张牌1。

假设R4=2,荷官在重复销去两次1 之后,还是将1 发给了玩家,这是因为1 是牌库中唯一的一张牌。


splay题解 by wgy

这道题还蛮简单的,用splay找出1-x的区间,然后把它转到后面去,再删除第一个数就好了

需吸氧

#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#pragma GCC optimize("-fgcse")
#pragma GCC optimize("-fgcse-lm")
#pragma GCC optimize("-fipa-sra")
#pragma GCC optimize("-ftree-pre")
#pragma GCC optimize("-ftree-vrp")
#pragma GCC optimize("-fpeephole2")
#pragma GCC optimize("-ffast-math")
#pragma GCC optimize("-fsched-spec")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("-falign-jumps")
#pragma GCC optimize("-falign-loops")
#pragma GCC optimize("-falign-labels")
#pragma GCC optimize("-fdevirtualize")
#pragma GCC optimize("-fcaller-saves")
#pragma GCC optimize("-fcrossjumping")
#pragma GCC optimize("-fthread-jumps")
#pragma GCC optimize("-funroll-loops")
#pragma GCC optimize("-fwhole-program")
#pragma GCC optimize("-freorder-blocks")
#pragma GCC optimize("-fschedule-insns")
#pragma GCC optimize("inline-functions")
#pragma GCC optimize("-ftree-tail-merge")
#pragma GCC optimize("-fschedule-insns2")
#pragma GCC optimize("-fstrict-aliasing")
#pragma GCC optimize("-fstrict-overflow")
#pragma GCC optimize("-falign-functions")
#pragma GCC optimize("-fcse-skip-blocks")
#pragma GCC optimize("-fcse-follow-jumps")
#pragma GCC optimize("-fsched-interblock")
#pragma GCC optimize("-fpartial-inlining")
#pragma GCC optimize("no-stack-protector")
#pragma GCC optimize("-freorder-functions")
#pragma GCC optimize("-findirect-inlining")
#pragma GCC optimize("-fhoist-adjacent-loads")
#pragma GCC optimize("-frerun-cse-after-loop")
#pragma GCC optimize("inline-small-functions")
#pragma GCC optimize("-finline-small-functions")
#pragma GCC optimize("-ftree-switch-conversion")
#pragma GCC optimize("-foptimize-sibling-calls")
#pragma GCC optimize("-fexpensive-optimizations")
#pragma GCC optimize("-funsafe-loop-optimizations")
#pragma GCC optimize("inline-functions-called-once")
#pragma GCC optimize("-fdelete-null-pointer-checks")
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <utility>
#include <cctype>
#define mid (l + r >> 1)

using namespace std;

const int SIZE = 7e5 + 5;
struct SPLAY {
	int siz;
	int val;
	int ch[2];
	int fa;
} T[SIZE];
int root, n, R[SIZE], tot;

template < typename T >
inline void read ( T &a ) {
	a = 0; T f = 1; char ch;
	while (!isdigit(ch = getchar())) if (ch == '-') f = -1;
	while (isdigit(ch)) a = (a << 3) + (a << 1) + (ch ^ '0'), ch = getchar();
	a *= f;
}

template < typename T >
inline T read ( T _checkType, bool _Typeflag ) {
    T f = 1, a = 0; char ch;
    while (!isdigit(ch = getchar())) if (ch == '-') f = -1;
    while (isdigit(ch)) a = (a << 3) + (a << 1) + (ch ^ '0'), ch = getchar();
    return a * f;
}
template < typename T >
inline void write ( T x, char end_, int st = 0 ) {
    if (x < 0) x = -x, putchar('-');
    if (x > 9) write(x / 10, end_, st + 1);
    putchar(x % 10 + '0');
    if (!st) putchar(end_);
}

inline void update(int u) {
	T[u].siz = T[T[u].ch[0]].siz + T[T[u].ch[1]].siz + 1;
}

inline int make(int l, int r, int fa) {
	int u = ++tot;
	T[u].siz = 1;
	T[u].val = mid;
	T[u].ch[0] = T[u].ch[1] = 0;
	T[u].fa = fa;
	if (mid > l) T[u].ch[0] = make(l, mid - 1, u);
	if (mid < r) T[u].ch[1] = make(mid + 1, r, u);
	update(u);
	return u;
}

inline void rotate(int x) {
	int y = T[x].fa;
	int z = T[y].fa;
	int w = T[y].ch[1] == x;
	T[z].ch[T[z].ch[1] == y] = x;
	T[x].fa = z;
	T[y].ch[w] = T[x].ch[w ^ 1];
	T[T[x].ch[w ^ 1]].fa = y;
	T[x].ch[w ^ 1] = y;
	T[y].fa = x;
	update(y), update(x);
}

inline void splay(int x, int goal) {
	for (; T[x].fa ^ goal; rotate(x)) {
		int y = T[x].fa;
		int z = T[y].fa;
		if (z ^ goal)
			T[y].ch[1] ^ x ^ T[z].ch[1] ^ y ? rotate(x) : rotate(y);
	}
	if (!goal) root = x;
}

inline int getRank(int x) {
	int u = root;
	while (233) {
		if (x <= T[T[u].ch[0]].siz) u = T[u].ch[0];
		else {
			x -= T[T[u].ch[0]].siz + 1;
			if (!x) return u;
			u = T[u].ch[1];
		}
	}
}

inline int getcard(int x) {
	if (x) {
		splay(getRank(x), 0);
		int u = root;
		root = T[u].ch[1];
		T[root].fa = 0;
		T[u].ch[1] = 0;
		update(u);
		splay(getRank(T[root].siz), 0);
		if (u) T[u].fa = root;
		if (root) T[root].ch[1] = u, update(root);
	}
	int ranker = getRank(1);
	int res = T[ranker].val;
	splay(ranker, 0);
	if (T[ranker].ch[1])
		T[root = T[ranker].ch[1]].fa = 0;
	return res;
}

signed main() {
	read(n);
	root = make(1, n, 0);
	for (int i = 1; i <= n; ++i) {
		read(R[i]);
		write(getcard(R[i] % T[root].siz), '
');
	}
	return 0;
}

权值线段树题解 by lyc:

对于这道题,平衡树 常数太大 不开O2基本都会超时。

所以我们用常数更小一些的线段树来解决这道题。

首先,这道题有7e5张牌,我们采用权值线段树的做法,每个节点记录它对应的区间还剩多少张牌。那么建树操作就很显然了对吧

然后我们考虑销牌操作,由于销牌后整个序列依旧按从小到大有序(1,2,3,4 ->1,3,4),我们设定一个指针now,指向当前牌堆里的最后一张牌在目前牌堆中大小的排名,初始为0(n%n),每次销掉a张牌,我们就将now向右移a位,使它指向销完牌后牌堆里的最后一张,再将now加一,得到当前排队顶的牌在牌堆中的排名,根据定义,在权值线段树中查找第now个还存在的数就得到了这次删去的牌的编号。输出后还要记得把这张牌在权值线段树中删掉,再把now减一,使它重新指向牌堆里的最后一张牌。

代码:

#include<cstdio>
int n,nodes[80000010],now,a;
void writing(int x)
{
	if(!x)	return;
	writing(x/10);
	putchar((x%10)+'0');
}
void read(int &hhh)
{
	int x=0;
	char c=getchar();
	while((c<'0')|(c>'9'))	c=getchar();
	x=c^'0';
	c=getchar();
	while((c<='9')&(c>='0'))
	{
		x=(x<<1)+(x<<3)+(c^'0');
		c=getchar();
	}
	hhh=x;
}
void build(int l,int r,int x)
{
	if(l==r)	nodes[x]=1;
	else
	{
		int mid=(l+r)>>1;
		build(l,mid,x<<1);
		build(mid+1,r,(x<<1)+1);
		nodes[x]=r-l+1;
	}
}
int kth(int l,int r,int x,int val)
{
	if(l^r)
	{
		int mid=(l+r)>>1;
		if(val<=nodes[x<<1])	return kth(l,mid,x<<1,val);
		else	return kth(mid+1,r,(x<<1)+1,val-nodes[x<<1]);
	}
	else	return l;
}
void del(int l,int r,int x,int val)
{
	--nodes[x];
	if(l^r)
	{
		int mid=(l+r)>>1;
		if(val<=mid)	del(l,mid,x<<1,val);
		else	del(mid+1,r,(x<<1)+1,val);
	}
}
int main()
{
	read(n);
	build(1,n,1);
	for(int i=n;i;--i)
	{
		read(a);
		now=(now+a)%i+1;
		int cur=kth(1,n,1,now);
		writing(cur);
		putchar('
');
		del(1,n,1,cur);
		--now;
	}
	return 0;
}

3.数列分块入门 8

给出一个长为 (n) 的数列,以及 (n) 个操作,操作涉及区间询问等于一个数 (c) 的元素,并将这个区间的所有元素改为 (c)


本题的重点在于并将这个区间的所有元素改为c

区间推平操作!

有了区间推平,我们第一时间会想到什么?线段树|分块

ODT!!!!

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <vector>
#include <string>
#include <cctype>
#include <utility>
#include <set>
#define IT set<TreeNode>::iterator

using namespace std;

inline int getInt() {
	int a = 0, f = 1; char ch;
	while (!isdigit(ch = getchar())) if (ch == '-') f = -1;
	while (isdigit(ch)) a = (a << 3) + (a << 1) + (ch ^ '0'), ch = getchar();
	return a * f;
}

inline void outInt(int x) {
	char buffer[20]; int length = 0;
	bool minus = x < 0; if (minus) x = -x;
	do { buffer[length++] = x % 10 + '0'; x /= 10; } while (x);
	if (minus) buffer[length++] = '-';
	do { putchar(buffer[--length]); } while (length);
}

struct TreeNode {
	int lef;
	int rig;
	mutable int val;
	
	TreeNode(int l, int r = -1, int v = 0) : lef(l), rig(r), val(v) {}
	friend bool operator < (const TreeNode &rhs) const {
		return lef < rhs.lef;
	}
} ;
set<TreeNode> st;
int n = getInt();

inline IT split(int pos) {
	IT it = st.lower_bound(TreeNode(pos));
	if (it != st.end() && it->lef == pos)
		return it;
	it--;
	int l = it->lef, r = it->rig;
	int v = it->val;
	st.erase(it);
	st.insert(TreeNode(l, pos - 1, v));
	return st.insert(TreeNode(pos, r, v)).first;
}

inline void assign(int l, int r, int v) {
	IT itr = split(r + 1), itl = split(l);
	st.erase(itl, itr);
	st.insert(TreeNode(l, r, v));
}

inline int query(int l, int r, int v) {
	IT itr = split(r + 1), itl = split(l);
	int res = 0;
	for (; itl != itr; ++itl) res += (itl->rig - itl->lef + 1) * (itl->val == v);
	return res;
}

signed main() {
	for (int i = 1; i <= n; ++i) {
		int x = getInt();
		st.insert(TreeNode(i, i, x));
	}
	for (int i = 1; i <= n; ++i) {
		int l = getInt();
		int r = getInt();
		int v = getInt();
		printf("%d
", query(l, r, v));
		assign(l, r, v);
	}
    return 0;
}

4.SP3267 DQUERY - D-query

(Given) (a) (sequence) (of) (n) (numbers) (and) (a) (number) (of) (d-queries.) (A) (d-query) (is) (a) (pair) ((i,) (j)) ((1) (≤) (i) (≤) (j) (≤) (n).) (For) (each) (d-query) ((i,) (j),) (you) (have) (to) (return) (the) (number) (of) (distinct) (elements) (in) (the) (subsequence) (a_i),(a_{i+1},cdots,a_j)


这道题的树状数组的做法是离线的。

首先按询问的右端点排序,然后用树状数组把没有出现过的值加上去,把出现过的减去,最后统计答案即可

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#define mid ((l + r) >> 1)

using namespace std;

const int SIZE = 1e6 + 5;
int n, m, tree[SIZE], ans[SIZE];
int list[SIZE], tag[SIZE];
pair < pair < int , int > , int > st[SIZE];

inline bool _rule(pair < pair < int , int > , int > x, pair < pair < int , int > , int > y) {
	return x.first.second < y.first.second;
}

signed main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) scanf("%d", &list[i]);
	scanf("%d", &m);
	for (int i = 1; i <= m; ++i) scanf("%d %d", &st[i].first.first, &st[i].first.second), st[i].second = i;
	sort(st + 1, st + 1 + m, _rule);
	int zblyc = 1;
	for (int i = 1; i <= m; ++i) {
		for (int j = zblyc; j <= st[i].first.second; ++j) {
			if (tag[list[j]]) for (int x = tag[list[j]]; x <= n; x += x & -x) tree[x]--;
			tag[list[j]] = j;
			for (int x = j; x <= n; x += x & -x) tree[x]++;
		}
		zblyc = st[i].first.second + 1;
		int lef = 0, rig = 0;
		for (int x = st[i].first.second; x; x -= x & -x) rig += tree[x];
		for (int x = st[i].first.first - 1; x; x -= x & -x) lef += tree[x];
		ans[st[i].second] = rig - lef;
	}
	for (int i = 1; i <= m; ++i) printf("%d
", ans[i]);
	return 0;
}

5.P1231 教辅的组成

注:本题我已搬运到OJ,数据已经齐全,略卡时间和空间,但均在合理范围,我的代码已通过,请求公开 Link

蒟蒻HansBug在一本语文书里面发现了一本答案,然而他却明明记得这书应该还包含一份练习题。

然而出现在他眼前的书多得数不胜数,其中有书,有答案,有练习册。已知一个完整的书册均应该包含且仅包含一本书、一本练习册和一份答案,然而现在全都乱做了一团。

许多书上面的字迹都已经模糊了,然而HansBug还是可以大致判断这是一本书还是练习册或答案,并且能够大致知道一本书和答案以及一本书和练习册的对应关系(即仅仅知道某书和某答案、某书和某练习册有可能相对应,除此以外的均不可能对应)。

既然如此,HansBug想知道在这样的情况下,最多可能同时组合成多少个完整的书册。


虽然这并不是数据结构的题,但我觉得搬上来也不错,毕竟网络流是个好东西

这道题我自己码了半天RE结果看了Siyuan的题解后才发现我add_edge传参传错了。话说为什么传参错会RE

大概讲一下这道题的思路吧

看到题目很容易想到跑费用流二分图匹配,但这道题不行。

为什么呢?

因为他有三个部分

那么直接跑最大流吧!

但这里还有一个问题,就是一本书作为中间的部分有可能会被重复利用绿色产品

Siyuan曾经这样说道:

因此我们就要引入拆点的思想。我们的目的是:即使一本书与多个联系册有关系,它流出的流量也只能是 1。所以我们把每个代表书的点拆成左右两个点,左边的点和练习册连边,右边的点和答案连边;当然左右对应点也要连一条容量为 1 的边

有了拆点的思想我们就可以直接跑最大流啦!

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>

using namespace std;

const int SIZE_N = 4e6 + 5;
const int SIZE_M = 1e6 + 5;
const int INF = 1 << 30;
int n1, n2, n3, m, tot = 1, s, t;
int path[SIZE_N], ter[SIZE_M];
int head[SIZE_N], edge[SIZE_M];
int _next[SIZE_N], ver[SIZE_M];
int depth[SIZE_N], cur[SIZE_N];

inline int getid(int p, int x) {
	switch (p) {
		case 1: return x;
		case 2: return n2 + x;
		case 3: return n2 + n1 + x;
		case 4: return n2 + (n1 << 1) + x;
	}
}

inline void add_edge(int from, int to, int dis) {
	ver[++tot] = to, edge[tot] = dis, _next[tot] = head[from], head[from] = tot;
}

inline int bfs() {
	memset(depth, 0, sizeof depth);
	memcpy(cur, head, sizeof head);
	queue < int > Q;
	Q.push(s), depth[s] = 1;
	while (!Q.empty()) {
		int x = Q.front(); Q.pop();
		for (int i = head[x]; i; i = _next[i]) {
			if (!depth[ver[i]] && edge[i]) depth[ver[i]] = depth[x] + 1, Q.push(ver[i]);
		}
	}
	return depth[t];
}

inline int dfs(int x, int flow) {
	if (x == t) return flow;
	int res = 0;
	for (int i = cur[x]; i && res < flow; i = _next[i]) {
		cur[x] = i;
		if (depth[ver[i]] == depth[x] + 1 && edge[i]) {
			int tmp = dfs(ver[i], min(edge[i], flow - res));
			if (tmp) edge[i] -= tmp, edge[i ^ 1] += tmp, res += tmp;
		}
	}
	if (res < flow) depth[x] = -1;
	return res;
}

inline int dinic() {
	int res = 0;
	for (int x; bfs(); ) while (x = dfs(s, INF)) res += x;
	return res;
}

signed main() {
	scanf("%d %d %d", &n1, &n2, &n3);
	scanf("%d", &m);
	for (int i = 1; i <= m; ++i) {
		int from, to;
		scanf("%d %d", &from, &to);
		add_edge(getid(1, to), getid(2, from), 1);
		add_edge(getid(2, from), getid(1, to), 0);
	}
	scanf("%d", &m);
	for (int i = 1; i <= m; ++i) {
		int from, to;
		scanf("%d %d", &from, &to);
		add_edge(getid(3, from), getid(4, to), 1);
		add_edge(getid(4, to), getid(3, from), 0);
	}
	for (int i = 1; i <= n1; ++i) add_edge(getid(2, i), getid(3, i), 1), add_edge(getid(3, i), getid(2, i), 0);
	s = 0, t = (n1 << 1) + n2 + n3 + 1;
	for (int i = 1; i <= n2; ++i) add_edge(s, getid(1, i), 1), add_edge(getid(1, i), s, 0);
	for (int i = 1; i <= n3; ++i) add_edge(getid(4, i), t, 1), add_edge(t, getid(4, i), 0);
	printf("%d
", dinic());
	return 0;
}

6.[USACO12OPEN]书架Bookshelf

当农夫约翰闲的没事干的时候,他喜欢坐下来看书。多年过去,他已经收集了 N 本书 (1 <= N <= 100,000), 他想造一个新的书架来装所有书。

每本书 i 都有宽度 W(i) 和高度 H(i)。书需要按顺序添加到一组书架上;比如说,第一层架子应该包含书籍1 ... k,第二层架子应该以第k + 1本书开始,以下如此。每层架子的总宽度最大为L(1≤L≤1,000,000,000)。每层的高度等于该层上最高的书的高度,并且整个书架的高度是所有层的高度的总和,因为它们都垂直堆叠。

请帮助农夫约翰计算整个书架的最小可能高度。

有N(1 <= N <= 100000)本书,每本书有一个宽度W(i),高度H(i),(1 <= H(i) <= 1,000,000; 1 <= W(i) <= L)。

现在有足够多的书架,书架宽度最多是L (1 <= L <= 1,000,000,000),把书按顺序(先放1,再放2.....)放入书架。某个书架的高度是该书架中所放的最高的书的高度。

将所有书放入书架后,求所有书架的高度和的最小值?


首先考虑(O(N^2))的暴力DP,很好写出以下的转移方程:

[F_i=min(F_j+max(H_{j+1},H_{j+2},cdots,H_i)) ]

其中满足

[W_{j+1},W_{j+2},cdots,W_ile L ]

不难发现(W)的限制满足单调性,所以我们可以通过二分得到

也就是说

[F_i=min(F_j+max(H_{j+1},H_{j+2},cdots,H_i)) ]

满足

[left_ile j ]

所以我们可以用线段树优化,维护区间赋值,区间最小值,单点修改三个操作。

但这样还不够,我们还需要开一个单调栈,当然单调队列也可以(不过我懒)

枚举每一个位置然后找出第一个大于(H_i)的位置(lef),将([lef+1,i])这段区间的值赋为(H_i)

进而可以发现我们只需要每次区间修改(H)的值,查询直接在这一段区间查询就好了

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <stack>
#define mid ((l + r) >> 1)
#define lson (k << 1)
#define rson (k << 1 | 1)

using namespace std;
typedef long long ull;

const int SIZE = 100000 + 5;
const ull INF = 1e18;
stack < ull > S;
ull n, limit, W[SIZE], H[SIZE], pre[SIZE];
ull list[SIZE], dp[SIZE], val[SIZE << 2];
ull minval[SIZE << 2], flag[SIZE << 2];

inline void make(int k, int l, int r) {
	minval[k] = val[k] = flag[k] = INF;
	if (l ^ r) make(lson, l, mid), make(rson, mid + 1, r);
}

inline void push(int k) {
	if (flag[k] ^ INF) {
		minval[lson] = val[lson] + flag[k];
		minval[rson] = val[rson] + flag[k];
		flag[lson] = flag[rson] = flag[k];
		flag[k] = INF;
	}
}

inline int update(int k, int l, int r, int x, int y, int v) {
	if (l >= x && r <= y) return minval[k] = val[k] + v, flag[k] = v, 0;
	push(k);
	if (mid >= x) update(lson, l, mid, x, y, v);
	if (mid < y) update(rson, mid + 1, r, x, y, v);
	minval[k] = min(minval[lson], minval[rson]);
	val[k] = min(val[lson], val[rson]);
	return 0;
}

inline void modify(int k, int l, int r, int x) {
	if (l ^ r) {
		push(k);
		if (mid >= x) modify(lson, l, mid, x);
		else modify(rson, mid + 1, r, x);
		minval[k] = min(minval[lson], minval[rson]);
		val[k] = min(val[lson], val[rson]);
	}
	else minval[k] = INF, val[k] = dp[l - 1]; 
}

inline ull query(int k, int l, int r, int x, int y) {
	if (l >= x && r <= y) return minval[k];
	push(k); ull res = INF;
	if (mid >= x) res = min(res, query(lson, l, mid, x, y));
	if (mid < y) res = min(res, query(rson, mid + 1, r, x, y));
	return res;
}

signed main() {
	scanf("%lld %lld", &n, &limit);
	make(1, 1, n);
	S.push(1);
	for (int i = 1; i <= n; ++i) scanf("%lld %lld", &H[i], &W[i]), list[i] = list[i - 1] + W[i];
	for (int i = 2; i <= n; ++i) {
		while (S.size() && H[i] > H[S.top()]) S.pop();
		if (S.size()) pre[i] = S.top();
		S.push(i);
	}
	for (int i = 1; i <= n; ++i) {
		modify(1, 1, n, i);
		if (pre[i] + 1 <= i) update(1, 1, n, pre[i] + 1, i, H[i]);
		int lef = lower_bound(list, list + 1 + i, list[i] - limit) - list;
		if (lef < i) dp[i] = query(1, 1, n, lef + 1, i);
	}
	printf("%lld
", dp[n]);
	return 0;
}

7.P1110 [ZJOI2007]报表统计

Q的妈妈是一个出纳,经常需要做一些统计报表的工作。今天是妈妈的生日,小Q希望可以帮妈妈分担一些工作,作为她的生日礼物之一。

经过仔细观察,小Q发现统计一张报表实际上是维护一个非负整数数列,并且进行一些查询操作。

在最开始的时候,有一个长度为N的整数序列,并且有以下三种操作:

INSERT i k:在原数列的第i个元素后面添加一个新元素kk;如果原数列的第ii个元素已经添加了若干元素,则添加在这些元素的最后(见下面的例子)
MIN_GAP:查询相邻两个元素的之间差值(绝对值)的最小值
MIN_SORT_GAP:查询所有元素中最接近的两个元素的差值(绝对值)
例如一开始的序列为5, 3, 15,3,1。

执行操作INSERT 2 9将得到:5, 3, 9, 15,3,9,1,此时MIN_GAP为22,MIN_SORT_GAP为22。

再执行操作INSERT 2 6将得到:5, 3, 9, 6, 15,3,9,6,1

注意这个时候原序列的第22个元素后面已经添加了一个99,此时添加的66应加在99的后面。这个时候MIN_GAP为22,MIN_SORT_GAP为11。

于是小Q写了一个程序,使得程序可以自动完成这些操作,但是他发现对于一些大的报表他的程序运行得很慢,你能帮助他改进程序么?


待坑,先留着

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <utility>
#define sgt_mid ((SGT[k].l + SGT[k].r) >> 1)
#define mid ((l + r) >> 1)
#define U int k, int l, int r
#define H int k, int x, int val
#define transfer SGT[k].val = min(SGT[lson].val, SGT[rson].val)
#define lson (k << 1)
#define rson (k << 1 | 1)
#define LRE lson, l, mid
#define RRE rson, mid + 1, r
#define LWA lson, x, val
#define RWA rson, x, val
#define int long long

using namespace std;

const int SIZE = 500000 + 5;
const int INF = 0x7fffffff;
int a[SIZE], b[SIZE], root;
int n, m, tot, MIN_GAP, MIN_SORT_GAP;
struct SPLAY {
	int ch[2];
	int val;
	int fa;
} SBT[SIZE];

struct TREE {
	int l;
	int r;
	int val;
} SGT[SIZE];

template < typename T >
inline T ads(T x) { return x > 0 ? x : -x; }

/*****************SplayArea*****************/

inline void rotate(int x) {
	int y = SBT[x].fa;
	int z = SBT[y].fa;
	int w = SBT[y].ch[1] == x;
	SBT[z].ch[SBT[z].ch[1] == y] = x;
	SBT[x].fa = z;
	SBT[y].ch[w] = SBT[x].ch[w ^ 1];
	SBT[SBT[x].ch[w ^ 1]].fa = y;
	SBT[x].ch[w ^ 1] = y;
	SBT[y].fa = x;
}

inline void splay(int x, int goal) {
	for (; SBT[x].fa ^ goal; rotate(x)) {
		int y = SBT[x].fa;
		int z = SBT[y].fa;
		if (z ^ goal)
			SBT[z].ch[1] ^ y ^ SBT[y].ch[1] ^ x ? rotate(x) : rotate(y);
	}
	if (!goal) root = x;
}

inline void insert(int x) {
	int u = root, fa = 0;
	while (u && SBT[u].val ^ x) fa = u, u = SBT[u].ch[x > SBT[u].val];
	if (u) return ;
	u = ++tot;
	SBT[u].fa = fa;
	if (fa) SBT[fa].ch[x > SBT[fa].val] = u;
	SBT[u].val = x;
	splay(u, 0);
}

inline void find(int x) {
	int u = root;
	if (!u) return ;
	while (SBT[u].ch[x > SBT[u].val] && SBT[u].val ^ x) u = SBT[u].ch[x > SBT[u].val];
	splay(u, 0);
}

inline int next_(int x, int f) {
	find(x);
	int u = root;
	if (SBT[u].val == x) return SBT[u].val;
	if (SBT[u].val < x && !f) return SBT[u].val;
	if (SBT[u].val > x && f) return SBT[u].val;
	u = SBT[u].ch[f];
	while (SBT[u].ch[f ^ 1]) u = SBT[u].ch[f ^ 1];
	return SBT[u].val;
}

/*****************EndSplay*****************/

/*****************SegmentTreeArea*****************/

inline void make(U) {
	SGT[k].l = l, SGT[k].r = r;
	if (l ^ r) make(LRE), make(RRE), transfer;
	else SGT[k].val = ads(a[l] - a[l - 1]);
}

inline void modify(H) {
	if (SGT[k].l ^ SGT[k].r)
		if (sgt_mid >= x) modify(LWA), transfer;
		else modify(RWA), transfer;
	else SGT[k].val = val;
}

/*****************EndSegmentTree*****************/

inline void Initialize() {
	MIN_GAP = INF, MIN_SORT_GAP = INF;
	scanf("%d %d", &n, &m);
	insert(INF), insert(-INF);
	a[0] = INF, a[n + 1] = INF;
	for (int i = 1; i <= n; ++i) {
		scanf("%d", &a[i]);
		if (i ^ 1) {
			int l_limit = next_(a[i], false), r_limit = next_(a[i], true);
			MIN_SORT_GAP = min(MIN_SORT_GAP, min(ads(l_limit - a[i]), ads(r_limit - a[i])));
		}
		insert(a[i]);
		b[i] = a[i];
	}
	make(1, 1, n);
	for (int i = 1; i <= m; ++i) {
		char S[SIZE];
		scanf("%s", S);
		if (*S == 'I') {
			int x, y;
			scanf("%d %d", &x, &y);
			int l_limit = next_(y, false);
			int r_limit = next_(y, true);
			MIN_SORT_GAP = min(MIN_SORT_GAP, min(ads(y - l_limit), ads(y - r_limit)));
			insert(y);
			MIN_GAP = min(MIN_GAP, ads(b[x] - y));
			modify(1, x + 1, ads(a[x + 1] - y));
			b[x] = y;
		}
		else if (S[4] == 'G') printf("%d
", min(MIN_GAP, SGT[1].val));
		else printf("%d
", MIN_SORT_GAP);
	}
}

signed main() {
	Initialize();
	return 0;
}

8.Link Cut Tree (动态树)

给定 n 个点以及每个点的权值,要你处理接下来的 m 个操作。
操作有四种,操作从 0 到 3 编号。点从 1 到 n 编号。

0 x y 代表询问从 x 到 y 的路径上的点的权值的 xor 和。保证 x 到 y 是联通的。

1 x y 代表连接 x 到 y,若 x 到 y 已经联通则无需连接。

2 x y 代表删除边 (x,y),不保证边 (x,y) 存在。

3 x y 代表将点 x 上的权值变成 y。


模板题没什么好说的,放一下代码就好了,如果要学LCT可以自己看博客(首先要学splay(not fhq-treap))

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <stack>

using namespace std;

const int SIZE = 3e5 + 5;
struct ReadNode {
	template < typename T>
	void operator >> (T &a) {
		a = 0; T f = 1; char ch;
		while (!isdigit(ch = getchar())) if (ch == '-') f = -1;
		while (isdigit(ch)) a = (a << 3) + (a << 1) + (ch ^ '0'), ch = getchar();
		a *= f;
	}
	
	template < typename T>
	void write(T x) {
		if (x < 0) x = -x, putchar('-');
		if (x > 9) write(x / 10);
		putchar(x % 10 + '0');
	}
	
	template < typename T>
	void operator << (T x) {
		write(x);
	}
} win;
int n, q, dis[SIZE];

/******************LinkCutTree******************/

class LinkCutTree {
private:
	struct TreeNode {
		int ch[2];
		int val;
		int sum;
		int rev;
		int fa;
	} T[SIZE + 5];
	int st[SIZE + 5];
	
	inline void exch(int &x, int &y) { x ^= y ^= x ^= y; }
	inline void reverse(int x) { exch(T[x].ch[0], T[x].ch[1]); T[x].rev ^= 1; }
	inline void link(int x, int y, int w) { T[T[x].fa = y].ch[w] = x; }
	inline bool push_up(int x) { return (T[x].sum = T[x].val ^ T[T[x].ch[0]].sum ^ T[T[x].ch[1]].sum), 1; }
	inline void push_down(int x) { T[x].rev && (reverse(T[x].ch[0]), reverse(T[x].ch[1]), T[x].rev = 0); }
	inline void makeroot(int x) { access(x); splay(x); reverse(x); }
	inline void split(int x, int y) { makeroot(x); access(y); splay(y); }
	inline bool isroot(int x) { return (T[T[x].fa].ch[0] ^ x && T[T[x].fa].ch[1] ^ x); }
	inline bool which(int x) { return T[T[x].fa].ch[1] == x; }
	inline void rotate(int x) { int y = T[x].fa, z = T[y].fa, w = which(x); !isroot(y) && (T[z].ch[which(y)] = x), T[x].fa = z, link(T[x].ch[w ^ 1], y, w), link(y, x, w ^ 1), push_up(y), push_up(x); }
	inline void splay(int x) { int y = x, top = 0; while (st[++top] = y, !isroot(y)) y = T[y].fa; while (top) push_down(st[top]), --top; while (!isroot(x)) y = T[x].fa, !isroot(y) && (rotate(which(x) ^ which(y) ? x : y), 0), rotate(x); }
	inline void access(int x) { for (int son = 0; x; x = T[son = x].fa) splay(x), T[x].ch[1] = son, push_up(x); }
	inline int getroot(int x) { access(x), splay(x); while (T[x].ch[0]) push_down(x), x = T[x].ch[0]; return splay(x), x; }
	
public:
	inline void init(int length, int *data) { for (int i = 1; i <= length; ++i) T[i].val = data[i]; }
	inline void connect(int x, int y) { makeroot(x), getroot(y) ^ x && (T[x].fa = y); }
	inline void erase(int x, int y) { makeroot(x), !(getroot(y) ^ x) && !(T[y].fa ^ x) && !(T[y].ch[0]) && (T[y].fa = T[x].ch[1] = 0, push_up(x)); }
	inline void insert(int x, int v) { splay(x), T[x].val = v; }
	inline int find(int x, int y) { return split(x, y), T[y].sum; }
} lct_mast;

/*****************EndLinkCutTree*****************/

signed main() {
	win >> n; win >> q;
	for (int i = 1; i <= n; ++i) win >> dis[i];
	lct_mast.init(n, dis);
	for (int i = 1; i <= q; ++i) {
		int opt, x, y;
		win >> opt; win >> x; win >> y;
		switch(opt) {
		case 0: win << lct_mast.find(x, y), puts(""); break;
		case 1: lct_mast.connect(x, y); break;
		case 2: lct_mast.erase(x, y); break;
		case 3: lct_mast.insert(x, y); break;
		}
	}
	return 0;
}
// 需要233.cpp

9.P5227 [AHOI2013]连通图

给定一个无向连通图和若干个小集合,每个小集合包含一些边,对于每个集合,你需要确定将集合中的边删掉后改图是否保持联通。集合间的询问相互独立

定义一个图为联通的当且仅当对于任意的两个顶点,都存在一条路径连接它们


暴力LCT,维护把时间当成权值的最大生成树

因为我们是离线算法,所以我们可以得到每一条边的删除时间

顺便维护一个权值最小的边的编号

%:include <cstdio>
%:include <iostream>
%:include <algorithm>
%:include <cstring>
%:include <queue>

using namespace std;

const int SIZE = 1e7 + 5;
const int INF = 0x7fffffff;
struct ReadNode <%
	template < typename T>
	void operator >> (T &a) <%
		a = 0; T f = 1; char ch;
		while (!isdigit(ch = getchar())) if (ch == '-') f = -1;
		while (isdigit(ch)) a = (a << 3) + (a << 1) + (ch ^ '0'), ch = getchar();
		a *= f;
	%>
	
	template < typename T>
	void write(T x) <%
		if (x < 0) x = -x, putchar('-');
		if (x > 9) write(x / 10);
		putchar(x % 10 + '0');
	%>
	
	template < typename T>
	void operator << (T x) <%
		write(x);
	%>
%> win;
int n, m;

/******************LinkCutTree******************/

class LinkCutTree <%
public:
	struct TreeNode <%
		int 	ch<:2:>;
		int 	fa;
		int 	val;
		int 	siz;
		int 	cnt;
		int 	miv;
		bool 	rev;
	%> T<:SIZE:>;

	inline void exch(int &x, int &y) <% x ^= y ^= x ^= y; %>
	inline void push1(int x) <% T<:x:>.siz = T<:T<:x:>.ch<:0:>:>.siz + T<:T<:x:>.ch<:1:>:>.siz +T<:x:>.cnt + (x <= n); x > n && (T<:x:>.miv = x); %>
	inline void push2(int x) <% (T<:x:>.val >= T<:T<:T<:x:>.ch<:0:>:>.miv:>.val) && (T<:x:>.miv = T<:T<:x:>.ch<:0:>:>.miv); (T<:T<:x:>.miv:>.val >= T<:T<:T<:x:>.ch<:1:>:>.miv:>.val) && (T<:x:>.miv = T<:T<:x:>.ch<:1:>:>.miv); %>
	inline void transfer(int x) <% push1(x); push2(x); %>
	inline bool which(int x) <% return T<:T<:x:>.fa:>.ch<:1:> == x; %>
	inline bool isroot(int x) <% return (T<:T<:x:>.fa:>.ch<:0:> ^ x) && (T<:T<:x:>.fa:>.ch<:1:> ^ x); %>
	inline void rotate(int x) <% int y = T<:x:>.fa, z = T<:y:>.fa, w = which(x), test = which(y), s = T<:x:>.ch<:w ^ 1:>; (!isroot(y)) && (T<:z:>.ch<:test:> = x); T<:y:>.ch<:w:> = s, T<:x:>.ch<:w ^ 1:> = y; (s) && (T<:s:>.fa = y); T<:x:>.fa = z, T<:y:>.fa = x; transfer(y); %>
	inline void reverse(int x) <% exch(T<:x:>.ch<:0:>, T<:x:>.ch<:1:>), T<:x:>.rev ^= 1; %>
	inline void push_down(int x) <% if (T<:x:>.rev) <% if (T<:x:>.ch<:0:>) reverse(T<:x:>.ch<:0:>); if (T<:x:>.ch<:1:>) reverse(T<:x:>.ch<:1:>); T<:x:>.rev = 0; %> %>
	inline void push_up(int x) <% (!isroot(x)) && (push_up(T<:x:>.fa), 1); push_down(x); %>
	inline void splay(int x) <% push_up(x); for (; !isroot(x); rotate(x)) (!isroot(T<:x:>.fa)) && ((which(x) == which(T<:x:>.fa) ? rotate(T<:x:>.fa) : rotate(x)), 1); transfer(x); %>
	inline void access(int x) <% for (int i = 0; x; x = T<:i = x:>.fa) splay(x), T<:x:>.cnt += T<:T<:x:>.ch<:1:>:>.siz, T<:x:>.cnt -= T<:T<:x:>.ch<:1:> = i:>.siz, transfer(x); %>
	inline void makeroot(int x) <% access(x), splay(x), reverse(x); %>
	inline void split(int x, int y) <% makeroot(y), access(x), splay(x); %>
	inline int getroot(int x) <% access(x), splay(x), push_down(x); for (; T<:x:>.ch<:0:>; push_down(x = T<:x:>.ch<:0:>)); return splay(x), x; %>

	inline void init(int length, int data) <% for (int i = 0; i <= length; ++i) T<:i:>.val = data; %>
	inline void connect(int x, int y) <% split(x, y), T<:y:>.fa = x, T<:x:>.cnt += T<:y:>.siz, transfer(x); %>
	inline void erase(int x, int y) <% split(x, y), T<:y:>.fa = T<:x:>.ch<:0:> = 0, transfer(x); %>
	inline bool find() <% return access(1), splay(1), (T<:1:>.siz == n); %>
%> lct_mast;

/*****************EndLinkCutTree*****************/

int now<:SIZE:>, num, ans<:SIZE:>, st<:SIZE:>, top;
struct EdgeNode <%
	int from;
	int to;
	int dis;
%> edge<:SIZE:>;
struct LycNode <%
	bool opt, tag;
	int idx;
%> H<:SIZE:>;

signed main() <%
	win >> n; win >> m;
	lct_mast.init(n, INF);
	for (int i = 1; i <= m; ++i) <%
		int x, y;
		win >> x; win >> y;
		edge<:i:> = <%x, y%>;
		now<:i:> = i;
		H<:++num:> = <%1, 0, i%>;
	%>
	int k, tot = m;
	win >> k;
	for (int i = 1; i <= k; ++i) <%
		int x;
		win >> x;
		for (int j = 1; j <= x; ++j) <%
			int y;
			win >> y;
			edge<:now<:y:>:>.dis = i - 1;
			lct_mast.T<:now<:y:> + n:>.val = i - 1;
			H<:++num:> = <%0, 0, now<:y:>%>;
			edge<:++tot:>.from = edge<:now<:y:>:>.from;
			edge<:tot:>.to = edge<:now<:y:>:>.to;
			now<:y:> = tot;
			st<:++top:> = now<:y:>;
		%>
		H<:++num:>.tag = true;
		if (i ^ k) while (top) H<:++num:> = <%1, 0, st<:top--:>%>;
	%>
	for (int i = 1; i <= m; ++i)
		if (!edge<:now<:i:>:>.dis)
			edge<:now<:i:>:>.dis = k, lct_mast.T<:now<:i:> + n:>.val = k;
	tot += n;
	for (int i = 1; i <= tot; ++i) lct_mast.T<:i:>.siz = 1;
	for (int i = 1; i <= num; ++i) <%
		if (H<:i:>.tag) puts(lct_mast.find() ? "Connected" : "Disconnected");
		else <%
			int j = H<:i:>.idx, from = edge<:j:>.from;
			int to = edge<:j:>.to, dis = edge<:j:>.dis;
			lct_mast.makeroot(from);
			if (H<:i:>.opt) <%
				if (lct_mast.getroot(to) == from) <%
					int mix = lct_mast.T<:from:>.miv;
					if (lct_mast.T<:mix:>.val >= dis) continue;
					lct_mast.erase(edge<:mix - n:>.from, mix);
					lct_mast.erase(edge<:mix - n:>.to, mix);
				%>
				lct_mast.connect(from, j + n);
				lct_mast.connect(to, j + n);
			%> else <%
				if (lct_mast.getroot(to) == from) <%
					lct_mast.transfer(j + n);
					if (!lct_mast.T<:j + n:>.fa && !lct_mast.T<:j + n:>.siz) continue;
					lct_mast.erase(edge<:j:>.from, j + n);
					lct_mast.erase(edge<:j:>.to, j + n);
				%>
			%>
		%>
	%>
	return 0;
%>

10.[TJOI2018]异或

现在有一颗以(1)为根节点的由(n)个节点组成的树,树上每个节点上都有一个权值(v_i)。现在有(Q)次操作,操作如下:

  • (1;x;y):查询节点(x)的子树中与(y)异或结果的最大值
  • (2;x;y;z):查询路径(x)(y)上点与(z)异或结果最大值

这是一道可持久化(01Trie)树+树链剖分的好题。

但是树剖是出了名的难打,仔细看题,可以发现只需要(DFS)序便可以解决问题

对于第一个操作,直接用(DFS)序维护(sub\_tree)的信息即可

对于第二个操作,可以考虑在(DFS)遍历整棵树的同时插入权值

查询的话直接上(LCA)

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>

using namespace std;

const int SIZE = (100000 + 5) << 1;
int head[SIZE], ver[SIZE];
int nxt[SIZE], edge[SIZE];
int n, q, fuck, a[SIZE];

inline void pushEdge(int x, int y, int z) {
	ver[++fuck] = y, edge[fuck] = z;
	nxt[fuck] = head[x], head[x] = fuck;
}

struct ZeroOneTrie {
	int root[SIZE], cnt;
	int trie[SIZE << 6][2];
	int frie[SIZE << 6];

	ZeroOneTrie() { root[0] = cnt = 1; }
    
	inline void insert(int last, int &lyc, int x) {
		int rt = lyc = ++cnt;
		for (int i = 30; ~i; --i) {
			int now = (x >> i) & 1;
			trie[rt][!now] = trie[last][!now];
			trie[rt][now] = ++cnt;
			rt = trie[rt][now];
			last = trie[last][now];
			frie[rt] = frie[last] + 1;
		}
	}

	inline int find(int l, int r, int x) {
		int res = 0;
		for (int i = 30; ~i; --i) {
			int now = (x >> i) & 1;
			if (frie[trie[r][!now]] - frie[trie[l][!now]]) {
				r = trie[r][!now];
				l = trie[l][!now];
				res |= 1 << i;
			} else {
				r = trie[r][now];
				l = trie[l][now];
			}
		}
		return res;
	}
} t0, t1;

int dfn[SIZE], L[SIZE], R[SIZE];
int num, fa[SIZE][30], depth[SIZE];

inline void dfs(int x, int pre) {
	t1.insert(t1.root[pre], t1.root[x], a[x]);
	fa[x][0] = pre;
	depth[x] = depth[pre] + 1;
	L[x] = ++num;
	dfn[num] = a[x];
	for (int i = head[x]; ~i; i = nxt[i]) if (ver[i] ^ pre) dfs(ver[i], x);
	R[x] = num;
}

inline int lca_mast(int x, int y) {
	if (depth[x] < depth[y]) swap(x, y);
	for (int i = 25; ~i; --i) if (depth[fa[x][i]] >= depth[y]) x = fa[x][i];
	if (x ^ y) {
		for (int i = 25; ~i; --i)
			if (fa[x][i] ^ fa[y][i]) x = fa[x][i], y = fa[y][i];
		return fa[x][0];
	} else return x;
}

signed main() {
	memset(head, -1, sizeof head);
	scanf("%d %d", &n, &q);
	for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
	for (int i = 1; i < n; ++i) {
		int from, to;
		scanf("%d %d", &from, &to);
		pushEdge(from, to, 1);
		pushEdge(to, from, 1);
	}
	dfs(1, 0);
	for (int j = 1; j <= 25; ++j)
		for (int i = 1; i <= n; ++i)
			fa[i][j] = fa[fa[i][j - 1]][j - 1];
	for (int i = 1; i <= n; ++i) t0.insert(t0.root[i - 1], t0.root[i], dfn[i]);
	for (int i = 1; i <= q; ++i) {
		int opt, x, y, z;
		scanf("%d %d %d", &opt, &x, &y);
		if (opt ^ 1) {
			scanf("%d", &z);
			int ans = lca_mast(x, y);
			printf("%d
", max(t1.find(t1.root[fa[ans][0]], t1.root[x], z), t1.find(t1.root[fa[ans][0]], t1.root[y], z)));
		} else {
			printf("%d
", t0.find(t0.root[L[x] - 1], t0.root[R[x]], y));
		}
	}
	return 0;
}
原文地址:https://www.cnblogs.com/orchid-any/p/13695912.html