两列同位同排名时距离最小;把第 2 列每根映射成第 1 列同排名伙伴的位置排列,最少交换次数等于它的逆序对数,树状数组 O(n log n)。
OJ: roj
题目 ID: 3609
难度:提高
标签:排序树状数组逆序对python
创建: 2026-10-02 10:29
更新: 2026-10-10 11:30
形式化题目
给定两个长度为
每次操作可以交换同一列内相邻两根火柴的位置,两列各自独立地操作任意多次。求让
样例 1:
正解
思路
朴素想法。 按题意直接贪心——每次挑一对“顺序不对”的相邻火柴交换——无法证明总次数最少、也不知道何时停止;能保证正确的朴素版本是:先定出目标配对,再用双重循环枚举
第一步:最小距离由什么决定。 交换不改变任一列的元素集合,所以
要
第二步:把交换次数变成逆序对。 一次相邻交换恰好交换排名序列的相邻两项。要把排列
于是问题变成:选一个目标排列
第三步:把
| 位置 |
1 | 2 | 3 | 4 |
|---|---|---|---|---|
| 第 1 列高度 |
2 | 3 | 1 | 4 |
| 第 1 列排名 |
2 | 3 | 1 | 4 |
| 第 2 列高度 |
3 | 2 | 1 | 4 |
| 第 2 列排名 |
3 | 2 | 1 | 4 |
| 第 1 列中排名 |
2 | 1 | 3 | 4 |
| 位置 |
1 | 2 | 3 | 4 |
|---|---|---|---|---|
| 第 1 列排名 |
1 | 3 | 4 | 2 |
| 第 2 列排名 |
1 | 4 | 2 | 3 |
| 第 1 列中排名 |
1 | 3 | 4 | 2 |
第四步:
代码
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-10 11:30
* update_at: 2026-10-10 11:30
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MOD=99999997;
void add(vector<int>&tr,int x){int n=tr.size()-1; while(x<=n){tr[x]++; x+=x&-x;}}
int sumq(vector<int>&tr,int x){int s=0; while(x){s+=tr[x]; x-=x&-x;} return s;}
int main(){ios::sync_with_stdio(false);cin.tie(nullptr); int n; if(!(cin>>n)) return 0; vector<ll>a(n),b(n),sa,sb; for(int i=0;i<n;i++) cin>>a[i]; for(int i=0;i<n;i++) cin>>b[i]; sa=a; sb=b; sort(sa.begin(),sa.end()); sort(sb.begin(),sb.end()); map<ll,int> ra,rb; for(int i=0;i<n;i++){ra[sa[i]]=i+1; rb[sb[i]]=i+1;} vector<int> pos(n+1); for(int i=0;i<n;i++) pos[ra[a[i]]]=i; vector<int> tr(n+1); ll inv=0; for(int i=0;i<n;i++){int v=pos[rb[b[i]]]+1; int le=sumq(tr,v); inv += i - le; add(tr,v);} cout << inv%MOD << '\n'; return 0;}#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-02 10:29
# update_at: 2026-10-02 10:29
import sys
MOD = 99_999_997 # 题面取模数:99,999,997
def rank_of(values: list[int]) -> dict[int, int]:
"""把一列高度映射成排名:最小的为 1(同列高度互不相同,无并列)。"""
return {value: rank for rank, value in enumerate(sorted(values), 1)}
def count_inversions(order: list[int]) -> int:
"""树状数组统计逆序对数:依次插入各值,用"已插入个数 - 前缀和"得到比它大的个数。
逆序对数 = 把 order 变为升序所需的最少相邻交换次数。
"""
size = len(order)
tree = [0] * (size + 1)
inversions = 0
for seen, value in enumerate(order, 1):
le = 0 # 已插入中小于等于 value 的个数
j = value
while j:
le += tree[j]
j -= j & -j
inversions += seen - 1 - le # 已插入的比 value 大的个数
j = value
while j <= size:
tree[j] += 1
j += j & -j
return inversions
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
n = next(data)
a = [next(data) for _ in range(n)]
b = [next(data) for _ in range(n)]
# 最小距离 ⇔ 两列按排名同位配对(排序不等式);距离只取决于配对关系,
# 所以把第 1 列原样当作目标排列,只重排第 2 列,总次数即两排列的 Kendall 距离。
rank_of_a = rank_of(a)
rank_of_b = rank_of(b)
# 第 2 列第 i 根要与第 1 列同排名的那根同位,记下它在第 1 列中的位置
pos_of_rank: dict[int, int] = {}
for pos, value in enumerate(a):
pos_of_rank[rank_of_a[value]] = pos
order = [pos_of_rank[rank_of_b[value]] for value in b]
# 下标从 0 起,平移成 1 起以适配树状数组
print(count_inversions([pos + 1 for pos in order]) % MOD)
if __name__ == "__main__":
solve()复杂度
- 时间复杂度:
——两列排序离散化 ,生成映射序列 ,树状数组统计逆序对 。 - 空间复杂度:
,排名字典、映射序列与树状数组各占 。 - 答案上界
,超过模数 ;Python 整数任意精度,计数过程中不必取模,输出前统一取模。
总结
- 最小距离 ⇔ 两列同位同排名:平方和展开后用排序不等式,且最小值只由配对关系决定,与目标排列的具体形态无关。
- 最少交换次数是排列之间的度量问题:三角不等式说明只重排一列即最优,答案 = 两列排名序列的 Kendall 距离。
- 把 Kendall 距离展开成"第 1 列中同排名伙伴的位置"这一排列,用树状数组
数逆序对,最后对 取模。