前言
五一网课的例题,但是网上没有详细的题解(其实就是都没放代码),所以来写一篇。题目可以在这里提交。
题目简述
有 \(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