动态逆序对查询 (分块 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;
}
posted @ 2023-03-20 17:25  春始于雪之下  阅读(24)  评论(0编辑  收藏  举报