首页 > 其他分享 >PKUSC2019 D1T1 题解

PKUSC2019 D1T1 题解

时间:2024-04-19 09:35:52浏览次数:20  
标签:10 idx int 题解 PKUSC2019 tree 哨卡 D1T1 逆序

前言

五一网课的例题,但是网上没有详细的题解(其实就是都没放代码),所以来写一篇。题目可以在这里提交。

题目简述

有 \(n\) (\(n \leq 5 \times 10 ^5\))个村庄排成一排,每个村庄里有一个人。第 \(i\) 个村庄里的人要去第 \(p_i\) 个村庄,且 \(p\) 是 \(1 \sim n\) 的一个排列。他们出行方式是每次交换相邻两个村庄的人。

现在政府要建立 \(m\) 个哨卡,第 \(i\) 个哨卡建立在村庄 \(q_i\) 与 \(q_i + 1\) 之间,每次交换的时候,如果中间有一个哨卡,或者交换双方有一个人经过了一个哨卡,则需要花费 \(1\) 的代价。

政府每年建设一个哨卡。人们好奇,对于前 \(i\) 年建设的 \(i\) 个哨卡,每个人都走到对应村庄的最小代价是多少呢?由于这个问题太难了,所以交给聪明的你去做啦。

题目分析

先观察样例:

样例 #1

样例输入 #1

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

样例输出 #1

15
18
23
26
28
29
31
31

只有第一天 \((5, 6)\) 之间的哨卡时,情况如下:

\[9,10,7,3,1{\color{red}{|}}5,8,6,2,4 \]

好像可以把两边都先排个序,不用花费:

\[1,3,7,9,10{\color{red}{|}}2,4,5,6,8 \]

然后先让第 \(5\) 个人走到 \(10\) 这个位置,分别和 \(2, 4, 5, 6, 8\) 交换了一次,贡献是 \(5\)。

\[1,3,7,9,2{\color{red}{|}}4,5,6,8,10 \]

然后再让第 \(4\) 个人走到 \(9\) 这个位置,分别和 \(2, 4, 5, 6, 8\) 交换了一次,贡献是 \(5\)。

\[1,3,7,2,4{\color{red}{|}}5,6,8,9,10 \]

然后再让第 \(3\) 个人走到 \(7\) 这个位置,分别和 \(2, 4, 5, 6\) 交换了一次,贡献是 \(4\)。

\[1,3,2,4,5{\color{red}{|}}6,7,8,9,10 \]

然后再让第 \(2\) 个人走到 \(3\) 这个位置,和 \(2\) 交换了一次,贡献是 \(1\)。

\[1,2,3,4,5{\color{red}{|}}6,7,8,9,10 \]

总花费 \(15\)。我们发现,这样移动一定是最优的,因为每个人的移动都是必要且最小的,是一个排序的过程。

再仔细看,要走到 \(10\) 这个位置,分别和 \(2, 4, 5, 6, 8\) 交换了一次,恰好是 \(10\) 和右边比 \(10\) 小的数。再以 \(2\) 来看,为了走到 \(2\),和左边比 \(2\) 大的 \(3,7,9,10\) 分别交换了一次。总结一下,跨过哨卡的逆序对都对答案贡献了一。

其实这就是结论:答案等于经过了任何一个哨卡的逆序对个数。接下来考虑证明它。

证明:

任意一对逆序对必然要交换一次,如果一对逆序对中间跨过了一个哨卡,必然需要花费代价,因此答案至少是这个。

将两个关卡之间的部分排序,任意一对逆序对只会在相邻的时候交换一次,交换了所有逆序对即表明它走到了终点,因此答案至多是这个。

以上可以说是这题核心思考了。接下来就可以写出暴力代码了:

read(n, m);
for (int i = 1; i <= n; ++i) read(p[i]);
for (int i = 1; i <= m; ++i){
	int x; read(x), mark[x] = true;
	long long res = 0;
	for (int a = 1; a <= n; ++a)
	for (int b = a + 1; b <= n; ++ b){
		if (p[a] > p[b]){
			bool flag = false;
			for (int x = a; x <= b - 1; ++x) if (mark[x]){
				flag = true;
				break;
			}
			res += flag;
		}
	}
	write(res, '\n');
}

时间复杂度 \(\Theta(m n^3)\),显然不是正解。考虑增量法,加入一个哨卡,求得只经过这个哨卡的逆序对,即左边一块没有经过哨卡的部分和右边一块没有经过哨卡的部分形成的逆序对数。但是并不好做,将区间拆分并不是我们熟悉的数据结构好做的(当然也可能是我太菜了),所以将询问倒转,考虑每次删掉一个哨卡,并删除这个仅经过这个哨卡的逆序对数,并合并区间。发现可以用线段树合并,同时记录形成了多少个逆序对。时间复杂度 \(\Theta(n \log n)\)。

代码

当然肯定要放代码的。

//#pragma GCC optimize(3)
//#pragma GCC optimize("Ofast", "inline", "-ffast-math")
//#pragma GCC target("avx", "sse2", "sse3", "sse4", "mmx")
#include <iostream>
#include <cstdio>
#define debug(a) cerr << "Line: " << __LINE__ << " " << #a << endl
#define print(a) cerr << #a << "=" << (a) << endl
#define file(a) freopen(#a".in", "r", stdin), freopen(#a".out", "w", stdout)
#define main Main(); signed main(){ return ios::sync_with_stdio(0), cin.tie(0), Main(); } signed Main
using namespace std;

int n, m, p[500010], q[500010];
long long ans[500010];

bool mark[500010];

struct Bit_Tree{
	constexpr inline int lowbit(const int x){
		return x & -x;
	}
	int tree[500010];
	void modify(int p, int v){
		for (int i = p; i <= n; i += lowbit(i)) tree[i] += v;
	}
	int query(int p){
		int res = 0;
		for (int i = p; i; i -= lowbit(i)) res += tree[i];
		return res;
	}
} yzh;

struct DSU{
	int fa[500010];
	void init(){ for (int i = 1; i <= n; ++i) fa[i] = i; }
	int & operator [] (const int x) { return fa[x]; }
	int get(int x){ return fa[x] == x ? x : fa[x] = get(fa[x]); }
} dsu;

struct Segment_Tree{
	struct node{
		int lson, rson;
		int sum;
	} tree[500010 * 40];
	int tot;
	void pushup(int idx){
		tree[idx].sum = tree[tree[idx].lson].sum + tree[tree[idx].rson].sum;
	}
	int merge(int idx, int oidx, int l, int r, long long & ans){
		if (!idx || !oidx) return idx | oidx;
		if (l == r) return tree[idx].sum += tree[oidx].sum, idx;
		int mid = (l + r) >> 1;
		ans += 1ll * tree[tree[idx].rson].sum * tree[tree[oidx].lson].sum;
		tree[idx].lson = merge(tree[idx].lson, tree[oidx].lson, l, mid, ans);
		tree[idx].rson = merge(tree[idx].rson, tree[oidx].rson, mid + 1, r, ans);
		return pushup(idx), idx;
	}
	void modify(int &idx, int l, int r, int p, int val){
		if (l > p || r < p) return;
		if (!idx) tree[idx = ++tot].sum = 0;
		tree[idx].sum += val;
		if (l == r) return;
		int mid = (l + r) >> 1;
		modify(tree[idx].lson, l, mid, p, val);
		modify(tree[idx].rson, mid + 1, r, p, val);
	}
} huan;

int root[500010];

void init(){
	// 获得初始答案,顺便初始化每一棵线段树
	mark[0] = mark[n] = true;
	for (int i = 1; i <= m; ++i) mark[q[i]] = true;
	for (int i = 1, to; i <= n; i = to + 1){
		for (to = i; ; ++to){
			dsu[to] = i, huan.modify(root[i], 1, n, p[to], 1);
			ans[m + 1] += i - 1 - yzh.query(p[to]);
			if (mark[to]) break;
		}
		for (to = i; ; ++to){
			yzh.modify(p[to], 1);
			if (mark[to]) break;
		}
	}
}

signed main(){
	read(n, m);
	for (int i = 1; i <= n; ++i) read(p[i]);
	for (int i = 1; i <= m; ++i) read(q[i]);
	init();
	for (int i = m; i >= 1; --i){
		// 处理询问,线段树合并 q[i] q[i] + 1
		int u = dsu.get(q[i]), v = dsu.get(q[i] + 1);
		root[u] = huan.merge(root[u], root[v], 1, n, ans[i]);
		dsu[v] = u, ans[i] = ans[i + 1] - ans[i];
	}
	for (int i = 1; i <= m; ++i) write(ans[i + 1], '\n');
	return 0;
}

标签:10,idx,int,题解,PKUSC2019,tree,哨卡,D1T1,逆序
From: https://www.cnblogs.com/XuYueming/p/18144269

相关文章

  • [题解]ABC282E Choose Two and Eat One
    ABC282EChooseTwoandEatOne又一个图论的回顾——Kruskal最小(最大)生成树算法。看到\(n\)的范围只有\(500\),应该没有什么特别的算法。那么我们考虑建一个*\(n\)个顶点的完全图,节点\(x\)到节点\(y\)的边权值就是\(x^y+y^x\)。然后跑一遍最大生成树,得到的和就是最大结果了。如......
  • RuntimeError: No CUDA GPUs are available问题解决
    RuntimeError:NoCUDAGPUsareavailable问题解决检查GPU是否可用importtorchiftorch.cuda.is_available():print("GPU可用")else:print("GPU不可用")显示当前可用的GPU数量importtorchprint("当前可用的GPU数量:",torch.cuda.device_count())P......
  • [题解]CF33C Wonderful Randomized Sum
    CF33CWonderfulRandomizedSum我们可以发现,如果两区间不交叉也不会影响到结果,所以我们只需要考虑不交叉的情况即可。我们所选择的前缀\(1\simi\)应满足区间和最小,后缀也一样。所以用两个数组\(lr,rl\)分别记录下\(1\simi\)(前缀)最小和、\(i\simn\)(后缀)最小和。然后枚举分割......
  • 问题解析
    查看内存总量[root@localhost~]#free-h----人性化显示内存使用情况totalusedfreesharedbuff/cacheavailableMem:1.8G329M270M9.1M1.2G1.2GSwap:4.0G0B......
  • [ABC240E] Ranges on Tree 题解
    [ABC240E]RangesonTree题解思路解析由题意可知,只要一个点的所有儿子节点都被确定了,那么当前节点也就被确定了。也就是说,只要确定了所有叶子节点,也就能一层层地确定所有节点,而叶子节点没有儿子节点不受此条件的约束,同时我们希望\(\max\limits^N_{i=1}R_i\)最小,所以我们把所......
  • 【面试准备】跨域问题解决方法
    跨域是什么浏览器对于javascript的同源策略的限制,是一种安全策略举例:用户登陆某个网站后,服务器在客户端写了一些cookie,如果cookie被其他网站读取,那么隐私信息就会泄漏,包含用户的登录状态等。跨域情况说明:域名不同域名相同,端口不同二级域名不同跨域问题解决jsonpngi......
  • P7177 [COCI2014-2015#4] MRAVI 题解
    思路。我们知道最初添加的液体越多,那么每个蚂蚁得到的液体也就越多,又因为标签里有深搜,所以可以用DFS+二分解决(感觉说了一通废话),算是比较常规的一种解法了。在此题中我们需要魔改一下建树,需在其中添加判断此边是否为超级管道和处理通过液体的百分比这两段代码。DFS和二分的代......
  • Multiplicity 题解
    \(\texttt{ProblemLink}\)简要题意给定一个序列\(a\),求\(\sum\limits_{i=1}^ncnt_i\)。\(cnt_i\)表示在\(a\)中选择长度为\(i\)的非空子序列\(b\)使得\(\forallj\in[1,i],j|b_j\)的数量。思路计数题,考虑dp。设\(f_{i,j}\)表示前\(i\)个数,选......
  • [题解][洛谷P1174] 打砖块
    题目分析n行m列的砖块,起始有k发子弹。每次可以用一发子弹,打碎某一列当前处于这一列最下面的那块砖,并且得到相应的得分。某些砖块打碎以后会获得一个砖块。求最大得分。题解可以看出是一道动态规划题。关键在于如何设计状态。先考虑砖块打碎不会得到子弹的情况:这个时候可以......
  • [题解] [NOIP 1999] 导弹拦截
    [NOIP1999]导弹拦截题目描述有若干枚导弹,每一枚导弹的高度是\(h_i\),导弹拦截系统每次拦截导弹都不能比上一次拦截的高度更高,导弹拦截没有冷却时间且第一次拦截的高度任意。问题1:一套系统最多能拦截多少导弹?问题2:拦截所有导弹最少需要多少个拦截系统?输入格式一行,若干个......