[题解][Codeforces]Codeforces Round #647 (Div. 1) 简要题解

  • 感受到了 CF 对英语不好的菜鸡的蔑视

  • 原题解

A

题意

  • 给定一个 (n) 节点 (m) 条边,没有重边和自环的无向图,一开始所有的点都没有标记

  • 每次可以选出一个没有标记的点 (u),找到一个最小的正整数 (x) 满足 (u) 的邻居中没有出现过标记 (x)

  • 然后把点 (u) 的标记设为 (x)

  • 求一种标记顺序,使得最后第 (i) 个点的标记为 (t_i)

  • (1le nle 5 imes10^5)(0le mle 5 imes10^5)(1le t_ile n)

做法:模拟

  • 正确地看懂了难懂的题目之后,就是一个 sb 模拟

  • 显然按 (t) 从小到大填,(t) 相同时顺序任意

  • 放标记的过程中判断无解即可

  • (O(n+m))

Code

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	if (bo) res = ~res + 1;
}

const int N = 5e5 + 5, M = N << 1;

int n, m, ecnt, nxt[M], adj[N], go[M], cur[N], ans[N];
bool vis[N];

struct elem
{
	int id, x;
} a[N];

inline bool comp(elem a, elem b)
{
	return a.x < b.x;
}

void add_edge(int u, int v)
{
	nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
	nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u;
}

int main()
{
	int x, y;
	read(n); read(m);
	while (m--) read(x), read(y), add_edge(x, y);
	for (int i = 1; i <= n; i++) read(x), a[i] = (elem) {i, x};
	std::sort(a + 1, a + n + 1, comp);
	for (int i = 1; i <= n; i++)
	{
		int u = a[i].id, oc = 0;
		for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
			if (cur[v])
			{
				if (cur[v] == a[i].x) return puts("-1"), 0;
				if (!vis[cur[v]]) vis[cur[v]] = 1, oc++;
			}
		if (oc < a[i].x - 1) return puts("-1"), 0;
		ans[i] = u; cur[u] = a[i].x;
		for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
			vis[cur[v]] = 0;
	}
	for (int i = 1; i <= n; i++) printf("%d ", ans[i]);
	return puts(""), 0;
}

B

题意

  • 给定 (n) 个数,第 (i) 个数为 (p^{k_i})

  • 把这些数分成两个集合,设这两个集合内的数之和分别为 (a)(b),求 ((min|a-b|)mod(10^9+7))

  • 多组数据,数据组数不超过 (10^5)(1le n,ple 10^6)(0le k_ile 10^6),所有数据的 (n) 之和不超过 (10^6)

做法:贪心

  • 特判 (p=1),答案为 (nmod 2)

  • 否则 (k) 越大越能对 (|a-b|) 起到决定性

  • 故按 (k) 从大到小贪

  • 具体地,把 (k) 相同的合并成一段,按 (k) 从大到小,设当前段长为 (l)

  • 如果当前答案为 (0),且 (l) 为奇数,则这一段的贡献为 (p^k)

  • 若当前答案不为 (0) 则选用让当前答案变得尽可能小的方法

  • 期间需要记录当前答案除以 (p^k) 后的结果(不能取模,大于 ( ext{INF}) 则设为 ( ext{INF})),需要一些特殊的技巧,细节较多

  • (O(nlog k)),注意实现姿势,避免复杂度被写成 (O(nlog k+k))

Code

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	if (bo) res = ~res + 1;
}

typedef long long ll;

const int N = 1e6 + 5, djq = 1e9 + 7;
const ll INF = 2e6;

int n, p, k[N], m, val[N], len[N];
ll pc[N];

int qpow(int a, int b)
{
	int res = 1;
	while (b)
	{
		if (b & 1) res = 1ll * res * a % djq;
		a = 1ll * a * a % djq;
		b >>= 1;
	}
	return res;
}

void work()
{
	read(n); read(p);
	for (int i = 1; i <= n; i++) read(k[i]);
	if (p == 1) return (void) printf("%d
", n & 1);
	pc[0] = 1;
	for (int i = 1; i <= 100; i++)
	{
		pc[i] = pc[i - 1] * p;
		if (pc[i] > INF) pc[i] = INF;
	}
	std::sort(k + 1, k + n + 1);
	m = 0;
	for (int i = 1, j = 0; i <= n; i++)
	{
		j++;
		if (i == n || k[i] < k[i + 1]) val[++m] = k[i], len[m] = j, j = 0;
	}
	int ans = 0; bool vis = 0; ll cur = 0;
	for (int i = m; i >= 1; i--, cur = std::min(cur * pc[val[i + 1] - val[i]], INF))
		if (!vis)
		{
			if (len[i] & 1) ans = qpow(p, val[i]), cur = 1, vis = 1;
		}
		else
		{
			if ((len[i] & 1) == (cur & 1))
			{
				if (len[i] >= cur) ans = 0, vis = 0, cur = 0;
				else ans = (ans - 1ll * len[i] * qpow(p, val[i]) % djq + djq) % djq,
					cur -= len[i];
			}
			else
			{
				if (len[i] >= cur) ans = qpow(p, val[i]), cur = 1;
				else ans = (ans - 1ll * len[i] * qpow(p, val[i]) % djq + djq) % djq,
					cur -= len[i];
			}
		}
	printf("%d
", ans);
}

int main()
{
	for (int i = 101; i < N; i++) pc[i] = INF;
	int T; read(T);
	while (T--) work(); 
	return 0;
}

C

题意

  • (2n) 个点,每个点的点权给定

  • 对于任意 (1le ile n)(2i-1)(2i) 之间已经有一条边

  • 可以在两个点之间加边,边权为这两个点权的异或和中,最低位的 (1) 所在的位(最低位为第 (0) 位,如果异或和为 (0) 则为 (20)

  • 加入 (n) 条边让这张图成为一个环,使得加入的所有边权最小值最大,并输出任意一种方案

  • (1le nle 5 imes10^5),点权在 ([0,2^{20})) 之间

做法:欧拉回路

  • 注意题面坑:let 2^k be the greatest power of two dividing u xor v

  • (k)(uoplus v) 中最高位的 (1) 所在的位(( imes)

  • (k) 为最大满足 (2^k) 整除 (uoplus v) 的自然数((sqrt{})

  • 注意到答案很小,可以枚举答案 (res)

  • 把每个点的点权模掉 (2^{res}),转化为只有两个权值相等的点之间可以连边

  • 于是想到建一张图,原来点 (2i-1)点权(2i) 的点权之间连一条无向边

  • 我们要求的就是这张图的一个欧拉回路

  • 通过欧拉回路存在性判定求出 (res) 之后,求一条欧拉回路即可

  • (O(20n))

Code

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	if (bo) res = ~res + 1;
}

const int N = 5e5 + 5, M = 1050000;

int n, a[N], b[N], res = 20, fa[M], cnt[M], ecnt = 1, nxt[M], adj[M], go[M],
len, ans[M];
bool vis[M];

void add_edge(int u, int v)
{
	nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
	nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u;
}

void dfs(int u)
{
	while (adj[u])
	{
		int e = adj[u]; adj[u] = nxt[e];
		if (vis[e]) continue; vis[e] = vis[e ^ 1] = 1;
		dfs(go[e]);
		ans[++len] = e & 1 ? e - 2 : e;
		ans[++len] = e - 1;
	}
}

int cx(int x)
{
	if (fa[x] != x) fa[x] = cx(fa[x]);
	return fa[x];
}

void zm(int x, int y)
{
	if ((x = cx(x)) != (y = cx(y))) fa[y] = x;
}

int get(int x, int T) {return (x & (1 << T) - 1) + 1;}

int main()
{
	read(n);
	for (int i = 1; i <= n; i++) read(a[i]), read(b[i]);
	for (int T = 1; T <= 20; T++)
	{
		for (int i = 1; i <= (1 << T); i++) fa[i] = i;
		memset(vis, 0, sizeof(vis)); memset(cnt, 0, sizeof(cnt));
		for (int i = 1; i <= n; i++) zm(get(a[i], T), get(b[i], T)),
			vis[get(a[i], T)] = vis[get(b[i], T)] = 1,
			cnt[get(a[i], T)]++, cnt[get(b[i], T)]++;
		int rt; bool is = 1;
		for (int i = 1; i <= (1 << T); i++)
			if (vis[i]) rt = i;
		for (int i = 1; i <= (1 << T); i++)
			if (vis[i] && cx(i) != cx(rt)) is = 0;
		for (int i = 1; i <= (1 << T); i++) if (cnt[i] & 1) is = 0;
		if (!is) {res = T - 1; break;}
	}
	std::cout << res << std::endl;
	memset(vis, 0, sizeof(vis));
	for (int i = 1; i <= n; i++) vis[get(a[i], res)] = vis[get(b[i], res)] = 1,
		add_edge(get(a[i], res), get(b[i], res));
	int rt;
	for (int i = 1; i <= (1 << res); i++) if (vis[i]) rt = i;
	memset(vis, 0, sizeof(vis));
	dfs(rt);
	for (int i = len; i >= 1; i--) printf("%d ", ans[i]);
	return puts(""), 0;
}

D

题意

  • 平面上给定 (n) 个互不相同的点,其中一个点是原点

  • 建一棵树,原点为根,一个不为原点的点的父亲为其到原点的线段上的第二个点,边长即为到父亲的欧几里得距离

  • 求选出 (k) 个不同的点,这些点两两距离和最小值

  • (2le kle nle 5 imes10^5)

做法:贪心

  • 好题,由于 B 细节调太久再加上 A 和 C 题目理解太久,这题没有去开,QAQAQ

  • 这种树的生成方式只会导致树的一个特殊性质:根的所有子树都为链

性质:对于根的任意子树,若选出了 (c) 个点,则最优方案下子树内深度最大的 (min(lfloorfrac k2 floor,c)) 个点都必须选出

  • 证明:

考虑把一个点的位置往子树内移动 (1) 个长度单位时答案会怎么变化
设子树内已经选出了 (s) 个点,则该点和这 (s) 个点的距离都会 (-1),到其他 (k-1-s) 个点的距离都会 (+1),贡献为 (k-1-2s)
故当 (s<lfloorfrac k2 floor) 时,这个贡献一定为正

  • 有了这个性质之后,就考虑每条边的贡献(被经过的次数乘长度),即一条边长为 (l),子树内有 (s) 个关键点的边,贡献为 (l imes s imes(k-s))

  • 假设每个子树内的点数都不超过 (lfloorfrac k2 floor),则按深度从大到小加点,若这是该子树内第 (i) 次加点,其到根的距离为 (d),则这个点到根上所有边的 (s(k-s)) 都会发生变化,也就是贡献为:

  • [d(i(k-i)-(i-1)(k-i+1))=d imes(k+1-2i) ]

  • 显然当 (i<lfloorfrac k2 floor) 时,其父亲的贡献比自己小,故把贡献排序之后选最大的 (k) 个即可

  • 然后考虑如果有子树内的点数超过 (lfloorfrac k2 floor) 怎么处理,显然这样的子树最多一个

  • 根据上面的贡献式,可以得出在一个已经选出 (lfloorfrac k2 floor) 个点的子树内再选点是负贡献

  • 故只有在前面排序后不够选出贡献最大的 (k) 个时,才采用这种策略

  • 同时由于选根的贡献为 (0),故这时根一定要选上

  • 故设前面已经选出了 (tot) 个正贡献的点,再选了一个根之后,还需要选出 (k-tot-1) 个点

  • 与前面性质的证明类似,可以得出这 (k-tot-1) 个点必然是某个子树中深度最浅的 (k-tot-1)

  • 枚举这个子树计算答案即可

  • (O(nlog n))

Code

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	if (bo) res = ~res + 1;
}

typedef long long ll;

const int N = 5e5 + 5;

int n, k, m, l[N], r[N], bel[N], tot;
double ans;
bool vis[N];

struct djq
{
	int u; double dis;
} w[N];

inline bool pmoc(djq a, djq b) {return a.dis > b.dis;}

struct point
{
	int x, y;
	
	friend inline ll operator * (point a, point b)
	{
		return 1ll * a.x * b.y - 1ll * a.y * b.x;
	}
	
	ll len() {return 1ll * x * x + 1ll * y * y;}
} a[N];

inline bool comp(point a, point b)
{
	bool isa = a.y > 0 || (a.y == 0 && a.x > 0),
		isb = b.y > 0 || (b.y == 0 && b.x > 0);
	if (isa != isb) return isa > isb;
	return a * b > 0 || (a * b == 0 && a.len() < b.len());
}

bool coll(point a, point b)
{
	bool isa = a.y > 0 || (a.y == 0 && a.x > 0),
		isb = b.y > 0 || (b.y == 0 && b.x > 0);
	return isa == isb && a * b == 0;
}

int main()
{
	read(n); read(k);
	for (int i = 1; i <= n; i++) read(a[i].x), read(a[i].y);
	for (int i = 1; i <= n; i++) if (!a[i].x && !a[i].y)
		{std::swap(a[i], a[1]); break;}
	std::sort(a + 2, a + n + 1, comp);
	for (int i = 2, j = 2; i <= n; i++)
		if (i == n || !coll(a[i], a[i + 1]))
		{
			l[++m] = j; r[m] = i;
			for (int k = j; k <= i; k++) bel[k] = m;
			for (int h = 1; h <= k / 2 && h <= i - j + 1; h++)
				w[++tot] = (djq) {i - h + 1, sqrt(a[i - h + 1].len())
					* (k + 1 - 2 * h)};
			j = i + 1;
		}
	std::sort(w + 1, w + tot + 1, pmoc);
	for (int i = 1; i <= tot && i <= k; i++) ans += w[i].dis, vis[w[i].u] = 1;
	if (tot < k)
	{
		double delta = -1e24;
		for (int T = 1; T <= m; T++)
		{
			int cnt = 0;
			for (int i = l[T]; i <= r[T]; i++) if (!vis[i]) cnt++;
			if (cnt < k - tot - 1) continue;
			double res = 0;
			for (int i = 1; i < k - tot; i++)
				res += sqrt(a[l[T] + (k - tot - 1) - i].len())
					* (k + 1 - 2 * (k / 2 + i));
			if (res > delta) delta = res;
		}
		ans += delta;
	}
	return printf("%.10lf
", ans), 0;
}

E

题意

  • 给定一个 (n) 个点 (m) 条边的强连通有向图

  • 定义一个点 (u) 是好的当且仅当 (u) 到所有的节点存在唯一简单路径

  • 求出所有好点,特殊地,如果好点的个数严格小于 (frac n5) 则输出 (-1)

  • (1le nle 10^5)(0le mle 2 imes10^5),多组数据,所有数据的 (n) 之和不超过 (10^5),所有数据的 (m) 之和不超过 (2 imes10^5)

做法:随机化+DFS树

  • 这是一道巧妙的图论题

  • 先考虑一个点 (u) 是好的条件:以 (u) 为根的 DFS 树唯一

  • 判断是否唯一只需以 (u) 为根后跑出任意一棵 DFS 树,判断是否所有的非树边都是后代指向祖先即可,证明比较显然

  • 考虑随便找一个点判断它是否是好的,随机 (100)

  • 如果找不到,就可以认为好的点数严格小于 (frac n5),出错的概率只有 (2 imes10^{-10})

  • 这样就找到了任意一个好点 (r),先跑一棵生成树

  • 考虑一个点 (u e r) 是好的条件:

性质 (1):若有超过一条边从 (u) 的子树内指向子树外,则 (u) 不是好的

  • 证明:

若以 (u) 为根,则原树上 (u) 子树外的点必然是 (u) 子树内的点的子孙
而如果这样的边有两条,则只有一条能成为树边,另一条必然违反了 (u) 为好点的条件

  • 由于所有的树边都是后代指向祖先,故 (u) 从子树内指向子树外的边可以简单预处理

  • 同时,由于原图强连通,故对于每个 (u),这样的返祖边一定存在

性质 (2):在性质 (1) 的前提下,设这条返祖边指向的点为 (v),则 (u) 是好的当且仅当 (v) 是好的

  • 证明:

充分性:如果 (v) 是好的,则考虑以 (v) 为根的 DFS 树,原树上 (u) 的子树在以 (v) 为根的 DFS 树上必然还是一棵子树,于是把 (u) 的父亲到 (u) 的边切断之后,(v) 仍然是好的。由于保证了就 (u) 的子树来说 (u) 是好的,又因为 (u) 的子树只有一条树边连向外面,故这样得到的生成树唯一,得证
必要性:如果 (u) 是好的,则这棵唯一的生成树一定是 (u) 的子树加上这条返祖边再加上子树外的部分,于是就 (u) 子树外的部分来说,(v) 是好的,同时 (u) 的子树内外只有返祖边,故 (v) 是好的,得证

  • 于是从上往下推即可

  • (O(T(n+m))),其中 (T) 为随机次数

Code

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	if (bo) res = ~res + 1;
}

const int N = 1e5 + 5, M = N << 1;

int n, m, ecnt, nxt[M], adj[N], st[M], go[M], tot, ow[M], ToT, dfn[N],
sze[N], seq[N], dep[N], f[N], cnt[N];
bool vis[N], ans[N], siv[M];

void add_edge(int u, int v)
{
	nxt[++ecnt] = adj[u]; adj[u] = ecnt; st[ecnt] = u; go[ecnt] = v;
}

void dfs(int u)
{
	vis[u] = 1; seq[dfn[u] = ++ToT] = u; sze[u] = 1;
	for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
		if (!vis[v]) dep[v] = dep[u] + 1, dfs(v), sze[u] += sze[v], siv[e] = 1;
		else ow[++tot] = e;
}

void work()
{
	int x, y, T = 100;
	read(n); read(m); ecnt = 0;
	for (int i = 1; i <= n; i++) adj[i] = 0;
	while (m--) read(x), read(y), add_edge(x, y);
	while (T--)
	{
		for (int i = 1; i <= n; i++) vis[i] = 0, cnt[i] = 0;
		for (int i = 1; i <= ecnt; i++) siv[i] = 0;
		int rt = (rand() << 15 | rand()) % n + 1, res = 1;
		tot = ToT = 0; dep[rt] = 1; dfs(rt); bool is = 1;
		for (int i = 1; i <= tot; i++)
		{
			int e = ow[i]; cnt[st[e]]++; cnt[go[e]]--;
			if (dfn[go[e]] > dfn[st[e]] || dfn[st[e]] >= dfn[go[e]] + sze[go[e]])
				{is = 0; break;}
		}
		if (!is) continue;
		for (int i = 1; i <= n; i++) f[i] = 0;
		ans[rt] = 1;
		for (int i = n; i >= 1; i--)
		{
			int u = seq[i];
			for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
				if (siv[e])
				{
					cnt[u] += cnt[v];
					if (f[v] && (!f[u] || dep[f[v]] < dep[f[u]]))
						f[u] = f[v];
				}
				else if (!f[u] || dep[go[e]] < dep[f[u]]) f[u] = go[e];
		}
		for (int i = 2; i <= n; i++)
			res += (ans[seq[i]] = cnt[seq[i]] == 1 && ans[f[seq[i]]]);
		if (res >= (n + 4) / 5)
		{
			for (int i = 1; i <= n; i++) if (ans[i]) printf("%d ", i);
			puts("");
		}
		else puts("-1");
		return;
	}
	puts("-1");
}

int main()
{
	int T; read(T);
	while (T--) work();
	return 0;
}

F

题意

  • 给定一个序列 (P_1>W_1>P_2>W_2>dots>W_{n-1}>P_n)

  • (P) 是一个 (1)(n) 的排列,(W) 是一个 (1)(n-1) 的排列,定义 (W_0=W_n=0)

  • 每次可以选择一对 (1le lle r<n) 满足 (min_{i=l}^rW_i>max(W_{l-1},W_{r+1}))

  • (W_{ldots r}) 中的最小值为 (W_m),把 (P_l)(P_m) 组成的段和 (P_{m+1})(P_{r+1}) 组成的段互换位置

  • 这种操作可以进行任意多次,有 (q) 次修改,每次交换 (P) 中的两个数,修改后求 (P) 能达到的逆序对个数最小值

  • (2le nle 2 imes10^5)(1le qle 5 imes10^4),保证两个排列和所有修改随机生成

做法:笛卡尔树

  • 考虑建出 (W) 的笛卡尔树,若 (W_i) 左边是空节点则在左边接上 (P_i),右边为空节点则在右边接上 (P_{i+1})

  • 不难发现这个互换操作实际上是互换了笛卡尔树上一个点的左右子树

  • 由于数据随机,故笛卡尔树深度可以视为 (O(log n))

  • 所以对于每个点,计算其左右子树之间的逆序对个数贡献,可以使用数据结构(推荐 pbds)把一侧的点插入之后在另一侧查,这样的复杂度是可以接受的

  • 计算出任意点 (u) 的左右子树之间贡献 (inv_u) 之后,答案即为:

  • [sum_umin(inv_u,size_{lc_u} imes size_{rc_u}-sum_u) ]

  • (size_u)(u) 子树内的叶子((P))个数

  • 考虑修改,由于只有 (O(log n)) 个节点(即对应叶子到根的路径)需要修改信息,故在这些节点上处理贡献即可

  • (O((n+q)log^2n))

Code

#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/hash_policy.hpp>

using namespace std;
using namespace __gnu_pbds;

template <class T>
inline void read(T &res)
{
	res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	if (bo) res = ~res + 1;
}

template <class T>
inline T Min(const T &a, const T &b) {return a < b ? a : b;}

typedef long long ll;
typedef tree <int, null_type, less<int>, rb_tree_tag,
tree_order_statistics_node_update> ds;

const int N = 2e5 + 5, M = N << 1, E = 20;

int n, p[N], w[N], len, a[M], RMQ[M][E], Log[M] = {-1}, lc[M], rc[M], fa[M], Root, q;
ds s[M];
ll inv[M], f[M];

int query(int l, int r)
{
	int k = Log[r - l + 1];
	return a[RMQ[l][k]] < a[RMQ[r - (1 << k) + 1][k]]
		? RMQ[l][k] : RMQ[r - (1 << k) + 1][k];
}

int build(int l, int r)
{
	int u = query(l, r); if (l == r) return u;
	fa[lc[u] = build(l, u - 1)] = u;
	fa[rc[u] = build(u + 1, r)] = u;
	return u;
}

void orz(int u, int val, int sgn)
{
	u = (u << 1) - 1; int v = u; u = fa[u];
	if (sgn < 0) s[v].erase(val); else s[v].insert(val);
	for (; u; u = fa[u], v = fa[v])
	{
		if (lc[u] == v) inv[u] += sgn * (int) (s[rc[u]].order_of_key(val));
		else inv[u] += sgn * ((int) (s[lc[u]].size() - s[lc[u]].order_of_key(val)));
		f[u] = f[lc[u]] + f[rc[u]] + Min(inv[u],
			(ll) (1ll * s[lc[u]].size() * s[rc[u]].size()) - inv[u]);
		if (sgn < 0) s[u].erase(val); else s[u].insert(val);
	}
}

int main()
{
	int x, y;
	read(n);
	for (int i = 1; i <= n; i++) read(p[i]);
	for (int i = 1; i < n; i++) read(w[i]);
	len = (n << 1) - 1;
	for (int i = 1; i <= len; i++) Log[i] = Log[i >> 1] + 1,
		a[i] = i & 1 ? n : w[i >> 1], RMQ[i][0] = i;
	for (int j = 1; j <= 18; j++)
		for (int i = 1; i + (1 << j) - 1 <= len; i++)
			RMQ[i][j] = a[RMQ[i][j - 1]] < a[RMQ[i + (1 << j - 1)][j - 1]]
				? RMQ[i][j - 1] : RMQ[i + (1 << j - 1)][j - 1];
	Root = build(1, len);
	for (int i = 1; i <= n; i++) orz(i, p[i], 1);
	read(q);
	while (q--) read(x), read(y), orz(x, p[x], -1), orz(y, p[y], -1),
		std::swap(p[x], p[y]), orz(x, p[x], 1), orz(y, p[y], 1),
			printf("%lld
", f[Root]);
	return 0;
}
原文地址:https://www.cnblogs.com/xyz32768/p/13062488.html