【逆序对 树状数组】P9843 [ICPC 2021 Nanjing R] Paimon Sorting|普及+
本文研究了Paimon排序算法的交换次数计算问题。该算法类似冒泡排序但具有特殊性质:每轮循环后前i个元素升序且a[i]为最大值。关键发现是当i>1时,交换次数等于当前元素与前面元素的逆序对数。利用树状数组高效计算逆序对,时间复杂度优化至O(n log n)。对于测试用例中的每个前缀序列,分别计算其交换次数,输出结果。通过分析算法特性和递推关系,实现了大规模数据的高效处理。
本文涉及知识点
P9843 [ICPC 2021 Nanjing R] Paimon Sorting
题目描述
派蒙刚刚发明了一种新的排序算法,看起来很像“冒泡排序”,但有一些不同之处。它接受一个长度为 n n n 的从 1 开始索引的序列 A A A 并对其进行排序。其伪代码如下所示。
// 排序算法
SORT(A)
for i from 1 to n // n 是 A 中元素的数量
for j from 1 to n
if a[i] < a[j] // a[i] 是 A 中的第 i 个元素
Swap a[i] and a[j]
如果你不相信这段算法可以对一个序列进行排序,你的任务就是证明它。无论如何,问题如下:
给定一个整数序列 A = a 1 , a 2 , ⋯ , a n A = a_1, a_2, \cdots, a_n A=a1,a2,⋯,an,对于其每个长度为 k k k 的前缀 A k A_k Ak(即,对于每个 1 ≤ k ≤ n 1 \le k \le n 1≤k≤n,考虑子序列 A k = a 1 , a 2 , ⋯ , a k A_k = a_1, a_2, \cdots, a_k Ak=a1,a2,⋯,ak),计算调用 SORT ( A k ) \text{SORT}(A_k) SORT(Ak) 时执行的交换次数。
输入格式
有多个测试用例。输入的第一行包含一个整数 T T T,表示测试用例的数量。对于每个测试用例:
第一行包含一个整数 n n n ( 1 ≤ n ≤ 10 5 1 \le n \le 10^5 1≤n≤105),表示序列的长度。
第二行包含 n n n 个整数 a 1 , a 2 , ⋯ , a n a_1, a_2, \cdots, a_n a1,a2,⋯,an ( 1 ≤ a i ≤ n 1 \le a_i \le n 1≤ai≤n),表示给定的序列。
保证所有测试用例的 n n n 之和不超过 10 6 10^6 106。
输出格式
对于每个测试用例,输出一行,包含 n n n 个用空格分隔的整数 s 1 , s 2 , ⋯ , s n s_1, s_2, \cdots, s_n s1,s2,⋯,sn,其中 s i s_i si 是调用 SORT ( A i ) \text{SORT}(A_i) SORT(Ai) 时执行的交换次数。
请不要在每行的末尾输出多余的空格,否则你的解答可能会被判为错误!
输入输出样例 #1
输入 #1
3
5
2 3 2 1 5
3
1 2 3
1
1
输出 #1
0 2 3 5 7
0 2 4
0
说明/提示
题面翻译由 ChatGPT-4o 提供。
树状数组 逆序对
性质一:i循环完后,a[1…i]升序,且a[i]是整个数组最大值。
推论一:i>1,i < j不会交换。
推论二:i >1 ,任意i时的交换次数为:f(i)= j < i ,且 a [ j ] > a [ i ] 的数量,即逆序对的数量。如果有多个 a [ j ] 相等,只算一次。 j < i,且a[j]>a[i]的数量,即逆序对的数量。如果有多个a[j]相等,只算一次。 j<i,且a[j]>a[i]的数量,即逆序对的数量。如果有多个a[j]相等,只算一次。
性质二:处理i1,不影响f(i2),i1 <i2。
过程:i为1时,模拟。i >1,逆序对。
时间复杂度:O(nnlogn)
通过ans[n]递推ans[n+1]。
令 M = max ( a n s [ 1... n ] ) M=\max(ans[1...n]) M=max(ans[1...n]),如果a[n+1]==M,则ans[n+1]=ans[n]。
如果ans[n+1]<M,效果和ans[n+1]==M,a[1…n]中大于a[n+1]的数量。 i ∈ [ i , n ] 时,结果完全一样 i\in[i,n]时,结果完全一样 i∈[i,n]时,结果完全一样
如果ans[n+1]>M,则ans[n+1]=ans[n]+iMax+1 。i=1时多交换一次,n+1时多交换一次。如果有两个或更多的M,令第二个M的下标是j1。则a[1…j1-1]的逆序对数不变,之前是M,现在是a[n+1]。
则a[j1…n-1]中的非M,逆序对+1,之前是M,现在是M和a[n+1];M逆序对也+1,增加了a[n+1],M)。故2 + (n-j1)
注意:此题写测试程序容易O(nnn),方便测试。
代码
核心代码
#include <iostream>
#include <sstream>
#include <vector>
#include<map>
#include<unordered_map>
#include<set>
#include<unordered_set>
#include<string>
#include<algorithm>
#include<functional>
#include<queue>
#include <stack>
#include<iomanip>
#include<numeric>
#include <math.h>
#include <climits>
#include<assert.h>
#include<cstring>
#include<list>
#include<array>
#include <bitset>
using namespace std;
template<class T1, class T2>
std::istream& operator >> (std::istream& in, pair<T1, T2>& pr) {
in >> pr.first >> pr.second;
return in;
}
template<class T1, class T2, class T3 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3>& t) {
in >> get<0>(t) >> get<1>(t) >> get<2>(t);
return in;
}
template<class T1, class T2, class T3, class T4 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3, T4>& t) {
in >> get<0>(t) >> get<1>(t) >> get<2>(t) >> get<3>(t);
return in;
}
template<class T1, class T2, class T3, class T4, class T5, class T6, class T7 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3, T4,T5,T6,T7>& t) {
in >> get<0>(t) >> get<1>(t) >> get<2>(t) >> get<3>(t) >> get<4>(t) >> get<5>(t) >> get<6>(t);
return in;
}
template<class T = int>
vector<T> Read() {
int n;
cin >> n;
vector<T> ret(n);
for (int i = 0; i < n; i++) {
cin >> ret[i];
}
return ret;
}
template<class T = int>
vector<T> ReadNotNum() {
vector<T> ret;
T tmp;
while (cin >> tmp) {
ret.emplace_back(tmp);
if ('\n' == cin.get()) { break; }
}
return ret;
}
template<class T = int>
vector<T> Read(int n) {
vector<T> ret(n);
for (int i = 0; i < n; i++) {
cin >> ret[i];
}
return ret;
}
template<int N = 1'000'000>
class COutBuff
{
public:
COutBuff() {
m_p = puffer;
}
template<class T>
void write(T x) {
int num[28], sp = 0;
if (x < 0)
*m_p++ = '-', x = -x;
if (!x)
*m_p++ = 48;
while (x)
num[++sp] = x % 10, x /= 10;
while (sp)
*m_p++ = num[sp--] + 48;
AuotToFile();
}
void writestr(const char* sz) {
strcpy(m_p, sz);
m_p += strlen(sz);
AuotToFile();
}
inline void write(char ch)
{
*m_p++ = ch;
AuotToFile();
}
inline void ToFile() {
fwrite(puffer, 1, m_p - puffer, stdout);
m_p = puffer;
}
~COutBuff() {
ToFile();
}
private:
inline void AuotToFile() {
if (m_p - puffer > N - 100) {
ToFile();
}
}
char puffer[N], * m_p;
};
template<int N = 1'000'000>
class CInBuff
{
public:
inline CInBuff() {}
inline CInBuff<N>& operator>>(char& ch) {
FileToBuf();
while (('\r' == *S) || ('\n' == *S) || (' ' == *S)) { S++; }//忽略空格和回车
ch = *S++;
return *this;
}
inline CInBuff<N>& operator>>(int& val) {
FileToBuf();
int x(0), f(0);
while (!isdigit(*S))
f |= (*S++ == '-');
while (isdigit(*S))
x = (x << 1) + (x << 3) + (*S++ ^ 48);
val = f ? -x : x; S++;//忽略空格换行
return *this;
}
inline CInBuff& operator>>(long long& val) {
FileToBuf();
long long x(0); int f(0);
while (!isdigit(*S))
f |= (*S++ == '-');
while (isdigit(*S))
x = (x << 1) + (x << 3) + (*S++ ^ 48);
val = f ? -x : x; S++;//忽略空格换行
return *this;
}
template<class T1, class T2>
inline CInBuff& operator>>(pair<T1, T2>& val) {
*this >> val.first >> val.second;
return *this;
}
template<class T1, class T2, class T3>
inline CInBuff& operator>>(tuple<T1, T2, T3>& val) {
*this >> get<0>(val) >> get<1>(val) >> get<2>(val);
return *this;
}
template<class T1, class T2, class T3, class T4>
inline CInBuff& operator>>(tuple<T1, T2, T3, T4>& val) {
*this >> get<0>(val) >> get<1>(val) >> get<2>(val) >> get<3>(val);
return *this;
}
template<class T = int>
inline CInBuff& operator>>(vector<T>& val) {
int n;
*this >> n;
val.resize(n);
for (int i = 0; i < n; i++) {
*this >> val[i];
}
return *this;
}
template<class T = int>
vector<T> Read(int n) {
vector<T> ret(n);
for (int i = 0; i < n; i++) {
*this >> ret[i];
}
return ret;
}
template<class T = int>
vector<T> Read() {
vector<T> ret;
*this >> ret;
return ret;
}
private:
inline void FileToBuf() {
const int canRead = m_iWritePos - (S - buffer);
if (canRead >= 100) { return; }
if (m_bFinish) { return; }
for (int i = 0; i < canRead; i++)
{
buffer[i] = S[i];//memcpy出错
}
m_iWritePos = canRead;
buffer[m_iWritePos] = 0;
S = buffer;
int readCnt = fread(buffer + m_iWritePos, 1, N - m_iWritePos, stdin);
if (readCnt <= 0) { m_bFinish = true; return; }
m_iWritePos += readCnt;
buffer[m_iWritePos] = 0;
S = buffer;
}
int m_iWritePos = 0; bool m_bFinish = false;
char buffer[N + 10], * S = buffer;
};
template<class ELE = int >
class ITreeArrSumOpe
{
public:
virtual void Assign(ELE& dest, const ELE& src) = 0;
virtual ELE Back(const ELE& n1, const ELE& n2) = 0;
};
template<class ELE = int >
class CTreeArrAddOpe :public ITreeArrSumOpe<ELE>
{
public:
virtual void Assign(ELE& dest, const ELE& src) {
dest += src;
}
virtual ELE Back(const ELE& n1, const ELE& n2) {
return n1 - n2;
}
};
template<class ELE = int, class ELEOpe = CTreeArrAddOpe<ELE> >
class CTreeArr
{
public:
CTreeArr(int iSize) :m_vData(iSize + 1)
{
}
void Add(int index, ELE value)
{
if ((index < 0) || (index >= m_vData.size() - 1)) { return; }
index++;
while (index < m_vData.size())
{
m_ope.Assign(m_vData[index], value);
index += index & (-index);
}
}
ELE Sum(int index)//[0...index]之和
{
index++;
ELE ret = 0;
while (index)
{
m_ope.Assign(ret, m_vData[index]);
index -= index & (-index);
}
return ret;
}
ELE Sum() { return Sum(m_vData.size() - 2); }
ELE Get(int index)
{
return m_ope.Back(Sum(index), Sum(index - 1));
}
private:
ELEOpe m_ope;
vector<ELE> m_vData;
};
class Solution {
public:
vector<long long> Ans(vector<int>& a) {
const int N = a.size();
CTreeArr<int> bit(N + 1);
vector<long long> ans = { 0 };
int iMax = a[0];
vector<vector<int>> inxs(N + 1);
inxs[a[0]].emplace_back(0);
int cnt = 1;
bit.Add(a[0], 1);
for (int i = 1; i < N; i++) {
long long add = 0;
if (a[i] > iMax) {
if (inxs[iMax].size() <= 1) {
add = 2;
}
else
{
add = 2 + (i - inxs[iMax][1]);
}
}
else {
add = cnt - bit.Sum(a[i]);
}
ans.emplace_back(ans.back() + add);
if (a[i] > iMax) {
iMax = a[i];
}
inxs[a[i]].emplace_back(i);
if (bit.Get(a[i]) > 0) { continue; }//重复数字
cnt++;
bit.Add(a[i], 1);
}
return ans;
}
};
int main() {
#ifdef _DEBUG
freopen("a.in", "r", stdin);
#endif // DEBUG
ios::sync_with_stdio(0); cin.tie(nullptr);
//CInBuff<> in; COutBuff<10'000'000> ob;
int T;
cin >> T;
for (int i = 0; i < T; i++)
{
auto a = Read<int>();
#ifdef _DEBUG
//printf("N=%d", N);
Out(a, ",a=");
//Out(ope, ",ope=");
#endif // DEBUG
auto res = Solution().Ans(a);
cout << res[0];
for (int i =1 ; i < res.size();i++)
{
cout << " " << res[i] ;
}
cout << "\n";
}
return 0;
};
单元测试
vector<int> a;
void Check(const vector<int>& a, const vector<long long>& res) {
const int N = a.size();
for (int i = 1; i <= N; i++) {
long long ans = 0;
vector<int> tmp(a.begin(), a.begin() + i);
for (int j = 0; j < i; j++) {
for (int k = 0; k < i; k++) {
if (tmp[j] < tmp[k]) {
swap(tmp[j], tmp[k]);
ans++;
}
}
}
Assert::AreEqual(ans, res[i - 1], to_wstring(i).c_str());
}
}
TEST_METHOD(TestMethod11)
{
a = { 2,3,2,1,5 };
auto res = Solution().Ans(a);
AssertV({ 0,2,3,5,7 }, res);
Check(a, res);
}
TEST_METHOD(TestMethod12)
{
a = { 1,2,3 };
auto res = Solution().Ans(a);
AssertV({ 0,2,4 }, res);
Check(a, res);
}
TEST_METHOD(TestMethod13)
{
a = { 1 };
auto res = Solution().Ans(a);
AssertV({ 0 }, res);
Check(a, res);
}
TEST_METHOD(TestMethod14)
{
a = { 1,1,2,3 };
auto res = Solution().Ans(a);
Check(a, res);
}
TEST_METHOD(TestMethod24)
{
a = { 4,2,2,2,1,5,7,8,9,12,15,4,5,2,1,1,2,3,4,20,20,21,22,22,23,24,1,25,27,28 };
auto res = Solution().Ans(a);
Check(a, res);
}

扩展阅读
| 我想对大家说的话 |
|---|
| 工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。 |
| 学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作 |
| 有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注 |
| 闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
| 子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
| 如果程序是一条龙,那算法就是他的是睛 |
| 失败+反思=成功 成功+反思=成功 |
视频课程
先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。
更多推荐

所有评论(0)