前言

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

题目简述

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

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

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

由于 yzh 喜欢在线的算法,所以她想让你每年立马告诉她答案哦。

题目分析

先观察样例:

样例 #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)\)。如果题目强制在线,那么可以用主席树加启发式合并,每次枚举小的区间,在大的区间中用主席树查询产生的逆序对个数,枚举时间复杂度 \(\Theta(n \log n)\),再算上主席树的 \(\log n\) 查询,时间复杂度是 \(\Theta(n \log ^ 2 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;
}

在线

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std; int n, q, h[500010]; struct Yzh_Is_The_President{
struct node{
int lson, rson;
int val;
} tree[500010 * 30];
int root[500010], tot;
void pushup(int idx){
tree[idx].val = tree[tree[idx].lson].val + tree[tree[idx].rson].val;
}
void build(int &idx, int l, int r){
tree[idx = ++tot] = {0, 0, 0};
if (l == r) return;
int mid = (l + r) >> 1;
build(tree[idx].lson, l, mid), build(tree[idx].rson, mid + 1, r);
}
void modify(int &idx, int oidx, int trl, int trr, int p, int v){
if (trl > p || trr < p) return;
tree[idx = ++tot] = tree[oidx];
if (trl == trr) return tree[idx].val += v, void();
int mid = (trl + trr) >> 1;
modify(tree[idx].lson, tree[oidx].lson, trl, mid, p, v);
modify(tree[idx].rson, tree[oidx].rson, mid + 1, trr, p, v);
pushup(idx);
}
int query(int lidx, int ridx, int trl, int trr, int l, int r){
if (trl > r || trr < l) return 0;
if (l <= trl && trr <= r) return tree[ridx].val - tree[lidx].val;
int mid = (trl + trr) >> 1;
return query(tree[lidx].lson, tree[ridx].lson, trl, mid, l, r) +
query(tree[lidx].rson, tree[ridx].rson, mid + 1, trr, l, r);
}
} yzh; struct Bit_Tree_Pre{
constexpr inline int lowbit(int x){
return x & -x;
}
int tree_min[500010], tree_max[500010];
inline void init(){ memset(tree_min, 0x3f, sizeof tree_min); }
inline void modify(int p){
for (int i = p; i <= n; i += lowbit(i)) tree_max[i] = max(tree_max[i], p);
for (int i = p; i; i &= i - 1) tree_min[i] = min(tree_min[i], p);
}
inline int query_min(int p){
int res = n + 1;
for (int i = p; i <= n; i += lowbit(i)) res = min(res, tree_min[i]);
return res;
}
inline int query_max(int p){
int res = 0;
for (int i = p; i; i &= i - 1) res = max(res, tree_max[i]);
return res;
}
} xym; long long ans; signed main() {
read(n, q);
yzh.build(yzh.root[0], 1, n);
for (int i = 1; i <= n; ++i) read(h[i]), yzh.modify(yzh.root[i], yzh.root[i - 1], 1, n, h[i], 1); xym.init(), xym.modify(n); long long lastans = 736520, p;
for (int i = 1; i <= q; ++i) {
read(p), p ^= lastans;
int l = xym.query_max(p) + 1, r = xym.query_min(p);
int L = p - l + 1, R = r - p; if (L > R){
for (int x = p + 1; x <= r; ++x) if (h[x] < n)
ans += yzh.query(yzh.root[l - 1], yzh.root[p], 1, n, h[x] + 1, n);
} else {
for (int x = l; x <= p; ++x) if (h[x] > 1)
ans += yzh.query(yzh.root[p], yzh.root[r], 1, n, 1, h[x] - 1);
} write(lastans = ans, '\n'); xym.modify(p);
}
return 0;
}

PKUSC2019 D1T1 题解的更多相关文章

  1. 洛谷P5524:[Ynoi2012]D1T1——题解

    https://www.luogu.org/problem/P5524 看着能做就当线段树复健题了. 根据高中知识我们有 $sin(a+b)=sin(a)cos(b)+cos(a)sin(b)$ $c ...

  2. PKUSC2019题解

    $D1T1$:$n$个村庄,第$i$个村庄的人要去第$p_i$个村庄(保证$p_i$为排列),每次可以将相邻两个村庄的人位置交换直到所有人都到达目的地.再给定一个长为$n-1$的排列$a$,表示第$i ...

  3. HEOI2016 题解

    HEOI2016 题解 Q:为什么要在sdoi前做去年的heoi题 A:我省选药丸 http://cogs.pro/cogs/problem/index.php?key=heoi2016 D1T1 树 ...

  4. pkuwc2018题解

    题解: 思路挺好想的..然而今天写代码写成傻逼了 d1t1: 首先比较暴力的就是$f[i][j]$表示i个这个点是j的概率 然后前缀和一下dp就是$n^2$的 部分分树形态随机就说明树深度是$log$ ...

  5. HNOI2018简要题解

    HNOI2018简要题解 D1T1 寻宝游戏 题意 某大学每年都会有一次 Mystery Hunt 的活动,玩家需要根据设置的线索解谜,找到宝藏的位置,前一年获胜的队伍可以获得这一年出题的机会. 作为 ...

  6. BJOI2018简要题解

    BJOI2018简要题解 D1T1 二进制 题意 pupil 发现对于一个十进制数,无论怎么将其的数字重新排列,均不影响其是不是 \(3\) 的倍数.他想研究对于二进制,是否也有类似的性质. 于是他生 ...

  7. CQOI2018简要题解

    CQOI2018简要题解 D1T1 破解 D-H 协议 题意 Diffie-Hellman 密钥交换协议是一种简单有效的密钥交换方法.它可以让通讯双方在没有事先约定密钥(密码)的情况下,通过不安全的信 ...

  8. NOIP2017 题解

    QAQ--由于没报上名并没能亲自去,自己切一切题聊以慰藉吧-- 可能等到省选的时候我就没有能力再不看题解自己切省选题了--辣鸡HZ毁我青春 D1T1 小凯的疑惑 地球人都会做,懒得写题解了-- D1T ...

  9. [2018HN省队集训D1T1] Tree

    [2018HN省队集训D1T1] Tree 题意 给定一棵带点权树, 要求支持下面三种操作: 1 root 将 root 设为根. 2 u v d 将以 \(\operatorname{LCA} (u ...

  10. HNOI/AHOI2018题解

    作为一名高二老年选手来补一下我省去年的省选题. D1T1:寻宝游戏 按顺序给出\(n\)个\(m\)位的二进制数\(a_i\),再在最前方添一个\(0\), 给出\(q\)次询问,每次询问给出一个同样 ...

随机推荐

  1. Github上优秀的.NET Core开源项目的集合【转】

    一般 ASP.NET Core Documentation - 官方ASP.NET核心文档站点. .NET Core Documentation - .NET Core,C#,F#和Visual Ba ...

  2. 『手撕Vue-CLI』自动安装依赖

    开篇 经过『手撕Vue-CLI』拷贝模板,实现了自动下载并复制指定模板到目标目录.然而,虽然项目已复制,但其依赖并未自动安装,可能需要用户手动操作,这并不够智能. 正如前文所述,我们已经了解了业务需求 ...

  3. Java JSON组成和解析

    本框架JSON元素组成和分析,JsonElement分三大类型JsonArray,JsonObject,JsonString. JsonArray:数组和Collection子类,指定数组的话,使用A ...

  4. XAF 属性编辑器(PropertyEditor)- 原理篇

    前言 随着 DEV24.1.3 的发布,XAF Blazor 中的属性编辑器(PropertyEditor)也进行了很大的改动,在使用体验上也更接近 WinForm 了,由于进行了大量的封装,理解上没 ...

  5. Python优雅遍历字典删除元素的方法

    在Python中,直接遍历字典并在遍历过程中删除元素可能会导致运行时错误,因为字典在迭代时并不支持修改其大小.但是,我们可以通过一些方法间接地达到这个目的. 1.方法一:字典推导式创建新字典(推荐) ...

  6. socket 端口复用 SO_REUSEPORT 与 SO_REUSEADDR

    背景 在学习 SO_REUSEADDR 地址复用的时候,看到有人提到了 SO_REUSEPORT .于是也了解了一下. SO_REUSEPORT 概述 SO_REUSEPOR这个socket选项可以让 ...

  7. 深度长文解析SpringWebFlux响应式框架15个核心组件源码

    Spring WebFlux 介绍 Spring WebFlux 是 Spring Framework 5.0 版本引入的一个响应式 Web 框架,它与 Spring MVC 并存,提供了一种全新的编 ...

  8. 如何将自己的网站从 HTTP 的转换为 HTTPS 的

    1. 获取 SSL/TLS 证书 首先,你需要获得一个 SSL/TLS 证书.你可以从以下来源之一获取证书: 免费证书: Let's Encrypt:一个免费的.自动化的证书颁发机构(CA),广泛使用 ...

  9. 最简GIF解析代码gif_jumper,用于stb_image的小改进

    gif jumper gif支持多帧动画,但是没有存储总帧数,解析gif直到结束才能知道总帧数. 所以gif解析代码,要么采用链表,要么不停realloc()分配内存,stb_image的代码就是如此 ...

  10. 解决方案 | MiKTex SSL connect error code 35

    可能是:你的网络屏蔽了需要连接的网站,更换手机流量热点即可解决