动态逆序对查询 (分块 OR 带修主席树)
首先对于读入的排列用树状数组计算逆序对数量,然后考虑分块,每一个块预先排序好,如果l和r中间有块那么对于每个块二分去找数量,然后l和r的块内进行暴力查找。
对于不在同一个块内的l和r我们考虑用vector的erase函数和insert函数进行有序的删除与查找。最后保留一下最小的答案即可。
分块做法
#include <iostream>
#include <cstring>
#include <iomanip>
#include <algorithm>
#include <stack>
#include <queue>
#include <numeric>
#include <cassert>
#include <bitset>
#include <cstdio>
#include <vector>
#include <unordered_set>
#include <cmath>
#include <map>
#include <unordered_map>
#include <set>
#include <deque>
#include <tuple>
#define int long long
#define all(a) a.begin(), a.end()
#define cnt0(x) __builtin_ctz(x)
#define endl '\n'
#define itn int
#define ll long long
#define ull unsigned long long
#define rep(i, a, b) for(int i = a;i <= b; i ++)
#define per(i, a, b) for(int i = a;i >= b; i --)
#define cntone(x) __builtin_popcount(x)
#define db double
#define fs first
#define se second
#define AC main(void)
#define HYS std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);
typedef std::pair<int, int > PII;
typedef std::pair<int, std::pair<int, int>> PIII;
typedef std::pair<ll, ll> Pll;
typedef std::pair<double, double> PDD;
using ld = double long;
const long double eps = 1e-9;
const int N = 1e5 + 10;
int n , m, _;
int a[N];
int block[N];
int sz[N];
int bsz;
namespace Fio {
inline std::string sread() {
std::string s = " ";
char e = getchar();
while (!isdigit(e) && !isalpha(e) && e != '*') e = getchar();
while (isdigit(e) || isalpha(e) || e == '*') s += e, e = getchar();
return s;
}
inline ll read() {
ll x = 0, y = 1;
char c = getchar();
while (!isdigit(c)) {
if (c == '-') y = -1;
c = getchar();
}
while (isdigit(c)) {
x = (x << 3) + (x << 1) + (c ^ 48);
c = getchar();
}
return x *= y;
}
inline void write(ll x) {
if (x < 0) x = -x, putchar('-');
ll sta[35], top = 0;
do sta[top++] = x % 10, x /= 10;
while (x);
while (top) putchar(sta[--top] + '0');
putchar('\n');
}
} using namespace Fio;
struct Fenwick{
int maxm, cnt = 0;
std::vector<int> tr;
Fenwick(int n): tr(n + 1, 0) {maxm = n;}
inline int lowbit(int x) {return x & -x;}
inline void add(int x, int v){
for(int i = x; i <= maxm; i += lowbit(i)) tr[i] += v;
cnt += v;
}
inline int query(int x){
int res = 0;
for(int i = x; i; i -= lowbit(i)) res += tr[i];
return res;
}
inline int query(int l, int r){
return query(r) - query(l - 1);
}
inline int find_kmin(int k) {
int ans = 0, cnt = 0;
for (int i = 20; i >= 0; i--) {
ans += (1 << i);
if (ans >= maxm || cnt + tr[ans] >= k) ans -= (1 << i);
else cnt += tr[ans];
}
return ans + 1;
}
inline int find_kmax(int k) {
return find_kmin(cnt - k + 1);
}
};
inline void solve(){
n = read();
Fenwick fk(n);
int res = 0;
for(int i = 1; i <= n; i ++){
a[i] = read();
res += i - fk.query(a[i]) - 1;
fk.add(a[i], 1);
}
bsz = sqrt(n) + 1;
std::vector<int> g[bsz + 1];
for(int i = 1; i <= n; i ++){
block[i] = i / bsz;
sz[block[i]] ++;
g[block[i]].push_back(a[i]);
}
for(int i = 0; i <= n / bsz; i ++) std::sort(all(g[i]));
m = read();
int ans = res;
while(m --){
int l, r;
l = read(), r = read();
if(l == r) continue;
int sum1 = 0;//存中间小于a[l]的数字数量
int sum2 = 0;//存中间小于a[r]的数字数量(不包括端点)
if(block[l] == block[r]){
for(int i = l + 1; i < r; i ++)
if(a[i] < a[l]) sum1 ++;
for(int i = l + 1; i < r; i ++)
if(a[i] < a[r]) sum2 ++;
ans -= sum1;
ans += (r - l - 1) - sum1;
ans += sum2;
ans -= (r - l - 1) - sum2;
if(a[l] < a[r]) ans ++;
else ans --;
res = std::min(res, ans);
std::swap(a[l], a[r]);
continue;
}
//块之间小于a[l]的数字的数量
for(int i = block[l] + 1; i < block[r]; i ++){
int tot = std::upper_bound(all(g[i]), a[l]) - g[i].begin();
sum1 += tot;
}
//块之间小于a[r]的数字的数量
for(int i = block[l] + 1; i < block[r]; i ++){
int tot = std::upper_bound(all(g[i]), a[r]) - g[i].begin();
sum2 += tot;
}
for(int i = l + 1; block[i] == block[l]; i ++)
if(a[i] < a[l]) sum1 ++;
for(int i = r - 1; block[i] == block[r]; i --)
if(a[i] < a[l]) sum1 ++;
for(int i = l + 1; block[i] == block[l]; i ++)
if(a[i] < a[r]) sum2 ++;
for(int i = r - 1; block[i] == block[r]; i --)
if(a[i] < a[r]) sum2 ++;
ans -= sum1;
ans += (r - l - 1) - sum1;
ans += sum2;
ans -= (r - l - 1) - sum2;
if(a[l] < a[r]) ans ++;
else ans --;
res = std::min(res, ans);
g[block[l]].insert(std::upper_bound(all(g[block[l]]), a[r]), a[r]);
g[block[r]].insert(std::upper_bound(all(g[block[r]]), a[l]), a[l]);
g[block[l]].erase(std::lower_bound(all(g[block[l]]), a[l]));
g[block[r]].erase(std::lower_bound(all(g[block[r]]), a[r]));
std::swap(a[l], a[r]);
}
//1 5 3 2 4
write(res);
}
signed AC{
//HYS
_ = read();
while(_ --)
solve();
return 0;
}
带修主席树做法
说实话感觉这个做法有点蠢,内存占用太大了还没有分块快。
#include <iostream>
#include <cstring>
#include <iomanip>
#include <algorithm>
#include <stack>
#include <queue>
#include <numeric>
#include <cassert>
#include <bitset>
#include <cstdio>
#include <vector>
#include <unordered_set>
#include <cmath>
#include <map>
#include <unordered_map>
#include <set>
#include <deque>
#include <tuple>
#define int long long
#define all(a) a.begin(), a.end()
#define cnt0(x) __builtin_ctz(x)
#define endl '\n'
#define itn int
#define ll long long
#define ull unsigned long long
#define rep(i, a, b) for(int i = a;i <= b; i ++)
#define per(i, a, b) for(int i = a;i >= b; i --)
#define cntone(x) __builtin_popcount(x)
#define db double
#define fs first
#define se second
#define AC main(void)
#define HYS std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);
typedef std::pair<int, int > PII;
typedef std::pair<int, std::pair<int, int>> PIII;
typedef std::pair<ll, ll> Pll;
typedef std::pair<double, double> PDD;
using ld = double long;
const long double eps = 1e-9;
const int N = 1e5 + 10;
int n , m, _;
int a[N];
namespace Fio {
inline std::string sread() {
std::string s = " ";
char e = getchar();
while (!isdigit(e) && !isalpha(e) && e != '*') e = getchar();
while (isdigit(e) || isalpha(e) || e == '*') s += e, e = getchar();
return s;
}
inline ll read() {
ll x = 0, y = 1;
char c = getchar();
while (!isdigit(c)) {
if (c == '-') y = -1;
c = getchar();
}
while (isdigit(c)) {
x = (x << 3) + (x << 1) + (c ^ 48);
c = getchar();
}
return x *= y;
}
inline void write(ll x) {
if (x < 0) x = -x, putchar('-');
ll sta[35], top = 0;
do sta[top++] = x % 10, x /= 10;
while (x);
while (top) putchar(sta[--top] + '0');
putchar('\n');
}
} using namespace Fio;
struct node{
int l, r, cnt;
}tr[N * 200];
struct Fenwick_Segment_Tree{//权值线段树动态开点加树状数组
int idx;
//s是树状数组需要用到的根节点
int S[N], maxm;
//查询时用到的两个数组
inline void init(){
idx = 0;
}
inline void clear(){
for(int i = 0; i <= n; i ++) S[i] = 0;
}
inline int lowbit(int x) {return x & -x;}
//需要用到的上一个版本root[i - 1](返回的值是当前版本的root) l,r是离散化后的区间范围 需要添加的离散化后的值x sum是添加的数量
inline int insert(int p, int l, int r, int x, int sum){
if(!p){
p = ++ idx;
tr[p] = {0, 0, 0};
}
if(l == r){
tr[p].cnt += sum;//新版本的信息加
return p;
}
int mid = l + r >> 1;
if(x <= mid) tr[p].l = insert(tr[p].l, l, mid, x, sum);//在左子树则需要更新信息,否则保留原本信息就可以
else tr[p].r = insert(tr[p].r, mid + 1, r, x, sum);
tr[p].cnt = tr[tr[p].l].cnt + tr[tr[p].r].cnt;
return p;
}
//位置为p 改变离散化后值为x的数量 数量
inline void add(int p, int x, int sum){
//maxm是树状数组数据离散化后的范围
while(p <= maxm){
S[p] = insert(S[p], 1, maxm, x, sum);
p += lowbit(p);
}
}
inline int query(int p, int L, int R, int val){
if(!p) return 0;
if(L > val) return 0;
if(R <= val) return tr[p].cnt;
int mid = L + R >> 1;
if(mid <= val)
return query(tr[p].l, L, mid, val) + query(tr[p].r, mid + 1, R, val);
return query(tr[p].l, L, mid, val);
}
//flag 表示是查询哪个数组 1 表示qr数组 0表示ql数组 查询的是左半边修改的数量总和
inline int Sum(int x, int val){
int res = 0;
while(x){
res += query(S[x], 1, maxm, val);
x -= lowbit(x);
}
return res;
}
}MHST;
inline void solve(){
n = read();
std::vector<int> nums;
for(int i = 1; i <= n; i ++)
a[i] = read();
MHST.maxm = n;
MHST.init();
auto &S = MHST.S;
int res = 0;
for(int i = 1; i <= n; i ++) MHST.add(i, a[i], 1);
for(int i = 1; i <= n; i ++){
int c1 = 0, c2 = 0;
c1 = MHST.Sum(i, a[i] - 1);
c2 = MHST.Sum(n, a[i] - 1);
res += c2 - c1;
}
int ans = res;
m = read();
while(m --){
int l, r;
l = read(), r = read();
if(l == r) continue;
int v1 = a[l], v2 = a[r];
int s1 = MHST.Sum(l - 1, v1 - 1);//l的左边区间比v1小的数量
int s2 = MHST.Sum(r, v1 - 1);//r左边比v小的数字的数量
res -= s2 - s1;//s2 - s1则是l-r之间小于v1的数字的数量
res += (r - l + 1) - (s2 - s1) - 1;//(r - l + 1) - (s2 - s1)则是l-r之间大于v1的数字数量 还要去掉v1
int s3 = MHST.Sum(l - 1, v2);;//l的左边区间比v2小的数量
int s4 = MHST.Sum(r, v2 - 1);//r左边比v2小的数字的数量
res += s4 - s3;//s4 - s3则为l-r之间比v2小的数字的数量
res -= (r - l) - (s4 - s3);
if(a[l] < a[r])
res --;
else if(a[l] > a[r]) res ++;
ans = std::min(ans, res);
MHST.add(l, v1, -1);
MHST.add(l, v2, 1);
MHST.add(r, v2, -1);
MHST.add(r, v1, 1);
std::swap(a[l], a[r]);
}
MHST.clear();
write(ans);
}
signed AC{
_ = 1;
_ = read();
while(_ --)
solve();
return 0;
}