归并排序求逆序对的主要思想就是将一个序列分成两半,保证两边数值的单调性;然后用左边的数值去更新右边的数值。而 C D Q CDQ CDQ分治同样是这样,将某一个序列分成两半,然后用左边的区间更新右边的区间,最终得到了答案。
事实上,我们对求逆序对的问题做一个转化:对于每一个数都可以看做是一份数对 ( x , y ) , x (x,y),x (x,y),x表示所处的数列中的位置,显然初始的都是 1 , 2 , 3 , . . . , n , 1,2,3,...,n, 1,2,3,...,n,而 y y y表示具体数值。那个每一个逆序对都可以表示为: x 1 < x 2 x_1<x_2 x1<x2且 y 1 > y 2 . y_1>y_2. y1>y2.
二维偏序问题,其实就是逆序对或者顺序对问题。或者定义的数对比较问题。我们如果题目中只给了一个数列,第一维是位置。其基本流程如下:
时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
这份代码以求顺序对为例:
#include
using namespace std;
inline int read(void)
{
int s = 0;char c = getchar();
while (c<'0' || c>'9') c = getchar();
while (c>='0' && c<='9') s = s*10+c-48,c = getchar();
return s;
}
struct node
{
int x,y,num,ans;
friend bool operator <= (node p1,node p2)
{
return p1.y <= p2.y;
}
} ;
int n;
node t[500000];
node a[500000];
void cdq(int l,int r)
{
int mid = l+r >> 1;
int h1 = l, h2 = mid+1, p = l-1;
if (l >= r) return;
cdq(l,mid);
cdq(mid+1,r);
while (h1 <= mid && h2 <= r)
{
if (a[h1] <= a[h2]) t[++p] = a[h1++];
else
{
a[h2].ans += h1-l;
t[++p] = a[h2++];
}
}
while (h1 <= mid) t[++p] = a[h1++];
while (h2 <= r) a[h2].ans += mid-l+1, t[++p] = a[h2++];
for (int i=l;i<=r;++i) a[i] = t[i];
return;
}
bool cmp(node p1,node p2)
{
if (p1.x == p2.x) return p1.y < p2.y;
return p1.x < p2.x;
}
bool compare(node p1,node p2)
{
return p1.num < p2.num;
}
int main(void)
{
freopen("2026.in","r",stdin);
freopen("2026.out","w",stdout);
n = read();
for (int i=1;i<=n;++i)
{
a[i].x = read();
a[i].y = read();
a[i].num = i;
a[i].ans = 0;
}
sort(a+1,a+n+1,cmp);
cdq(1,n);
sort(a+1,a+n+1,compare);
for (int i=1;i<=n;++i) printf("%d\n", a[i].ans);
return 0;
}
我们设数对为(修改时间,修改数值)。由于不同的操作,我们使其赋予不同的类型。对于区间查询,可以得到区间1-右端点的数值,再得到1-左端点的数值,两者相减即可。
我们对于这一些操作进行 C D Q CDQ CDQ分治:
时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
代码如下:
#include
using namespace std;
struct node
{
int type, index, val;
friend bool operator < (node p1,node p2)
{
if (p1.index == p2.index) return p1.type<p2.type;
return p1.index<p2.index;
}
} ;
int tot,cnt,n,m;
int ans[500000];
node t[500000];
node query[500000];
inline int read(void)
{
int s = 0;char c = getchar();
while (c<'0' || c>'9') c = getchar();
while (c>='0' && c<='9') s = s*10+c-48,c = getchar();
return s;
}
void add(int a,int b,int c)
{
tot ++;
query[tot].type = a;
query[tot].index = b;
query[tot].val = c;
return;
}
void CDQ(int l,int r)
{
if (l >= r) return;
int mid = l+r >> 1;
CDQ(l,mid), CDQ(mid+1,r);
int h1 = l,h2 = mid+1,p = l-1, sum = 0;
while (h1 <= mid && h2 <= r)
{
if (query[h1] < query[h2])
{
if (query[h1].type == 1) sum += query[h1].val;
t[++p] = query[h1++];
}
else
{
if (query[h2].type == 2) ans[query[h2].val] -= sum;
if (query[h2].type == 3) ans[query[h2].val] += sum;
t[++p] = query[h2++];
}
}
while (h1 <= mid) t[++p] = query[h1++];
while (h2 <= r)
{
if (query[h2].type == 2) ans[query[h2].val] -= sum;
if (query[h2].type == 3) ans[query[h2].val] += sum;
t[++p] = query[h2++];
}
for (int i=l;i<=r;++i) query[i] = t[i];
return;
}
int main(void)
{
freopen("shulie.in","r",stdin);
freopen("shulie.out","w",stdout);
tot = cnt = 0;
n = read();
for (int i=1;i<=n;++i)
{
int x = read();
add(1,i,x);
}
m = read();
for (int i=1;i<=m;++i)
{
int type;
char c = getchar();
while (c != 'S' && c != 'A') c = getchar();
if (c == 'S') type = 2;
if (c == 'A') type = 1;
int a = read(), b = read();
if (type == 1) add(type,a,b);
else add(2,a-1,++cnt),add(3,b,cnt);
//2表示左端点,3表示右端点
}
CDQ(1,tot);
for (int i=1;i<=cnt;++i) printf("%d%c",ans[i],i==cnt?'\n':'\n');
return 0;
}
方法1:
C D Q CDQ CDQ分治+树状数组。先将第一维排序,然后用 c d q cdq cdq分治处理出第二维,因为需要求出第三维的逆序对,每一次比较的时候如果取左边区间则把这个数放进树状数组里;如果是右边的数则累加树状数组内的答案。
时间复杂度: O ( N l o g N l o g V ) O(N\ logN\ logV) O(N logN logV)
方法2:
两遍 C D Q CDQ CDQ分治。同样将第一维进行排序,用 c d q cdq cdq分治处理出第二维,并标记是所属的左区间还是所属的右区间。在当前合并完以后,即在结束这一个函数直接,再调用第二个 c d q cdq cdq函数。这个函数是用来统计第三维的答案,如果取的是做区间且在第二维标记的做区间,和累加数值;若取得是右区间且在第二维中标记的也是右区间,则累加答案。
时间复杂度: O ( n l o g 2 n ) O(n\ log^2n) O(n log2n)
代码如下:
#include
using namespace std;
int n,r;
struct node
{
int a,b,c,pos,num;
friend bool operator < (node p1,node p2)
{
if (p1.a ^ p2.a) return p1.a<p2.a;
if (p1.b ^ p2.b) return p1.b<p2.b;
return p1.c<p2.c;
}
friend bool operator == (node p1,node p2) {
return p1.a == p2.a && p1.b == p2.b && p1.c == p2.c;
}
} ;
int CNT[500000];
int ans[500000];
node a[500000];
node t[500000];
node T[500000];
inline int read(void)
{
int s = 0;char c = getchar();
while (c<'0' || c>'9') c = getchar();
while (c>='0' && c<='9') s = s*10+c-48,c = getchar();
return s;
}
void CDQ(int l,int r)
{
int mid = l+r >> 1;
if (l >= r) return ;
CDQ(l,mid);
CDQ(mid+1,r);
int h1 = l, h2 = mid+1, k = l-1, sum = 0;
while (h1 <= mid && h2 <= r)
{
if (t[h1].c <= t[h2].c)
{
if (t[h1].pos == 0) sum ++;
//如果是左区间则答案合法
T[++k] = t[h1++];
}
else
{
if (t[h2].pos == 1) ans[t[h2].num] += sum;
//如果是右区间则统计答案
T[++k] = t[h2++];
}
}
while (h1 <= mid) T[++k] = t[h1++];
while (h2 <= r)
{
if (t[h2].pos == 1) ans[t[h2].num] += sum;
T[++k] = t[h2++];
}
for (int i=l;i<=r;++i) t[i] = T[i];
return;
}
void Cdq(int l,int r)
{
int mid = l+r >> 1;
if (l >= r) return ;
Cdq(l,mid);
Cdq(mid+1,r);
int h1 = l, h2 = mid+1, k = l-1;
while (h1 <= mid && h2 <= r)
{
if (a[h1].b <= a[h2].b) a[h1].pos = 0, t[++k] = a[h1++];
//在相等的情况下让第一维大的先进
else a[h2].pos = 1, t[++k] = a[h2++];
//pos记录左右区间,保证第一维的正确性
}
while (h1 <= mid) a[h1].pos = 0, t[++k] = a[h1++];
while (h2 <= r ) a[h2].pos = 1, t[++k] = a[h2++];
for (int i=l;i<=r;++i) a[i] = t[i];
CDQ(l,r);
return;
}
int main(void)
{
freopen("flower.in","r",stdin);
freopen("flower.out","w",stdout);
n = read();
r = read();
for (int i=1;i<=n;++i)
{
a[i].a = read();
a[i].b = read();
a[i].c = read();
a[i].num = i;
}
sort(a+1, a+n+1);
for (int i=n;i;--i)
if (a[i] == a[i+1])
ans[a[i].num] = ans[a[i+1].num]+1;
Cdq(1,n);
for (int i=1;i<=n;++i) CNT[ans[i]] ++;
for (int i=0;i<n;++i)
printf("%d\n", CNT[i]);
return 0;
}
题目相当为二维树状数组的问题。
做法是:三维偏序问题,标记通以为求和问题。
代码如下:
#include
using namespace std;
int n,m;
inline int read(void)
{
int s = 0;char c = getchar();
while (c<'0' || c>'9') c = getchar();
while (c>='0' && c<='9') s = s*10+c-48,c = getchar();
return s;
}
struct node
{
int type,x,y,val;
friend bool operator <= (node p1,node p2)
{
if (p1.x ^ p2.x) return p1.x<p2.x;
return p1.type<p2.type;
}
} ;
int ans[5000000];
node a[5000000];
node t[5000000];
struct Tree
{
int S[20010000];
#define lowbit(i) (i&-i)
void add(int x,int v)
{
for (int i=x;i<=n;i+=lowbit(i))
S[i] += v;
return;
}
int ask(int x)
{
int sum = 0;
for (int i=x;i;i-=lowbit(i))
sum += S[i];
return sum;
}
} ;
Tree tree;
void add(int A,int c,int d,int e)
{
m ++;
a[m].type = A;
a[m].x = c;
a[m].y = d;
a[m].val = e;
return;
}
void cdq(int l,int r)
{
int mid = l+r >> 1;
if (l >= r) return ;
cdq(l, mid);
cdq(mid+1, r);
int h1 = l, h2 = mid+1, k = l-1;
while (h1 <= mid && h2 <= r)
{
if (a[h1] <= a[h2])
{
if (a[h1].type == 1) tree.add(a[h1].y,a[h1].val);
t[++k] = a[h1++];
}
else
{
if (a[h2].type == 2) ans[a[h2].val] -= tree.ask(a[h2].y);
if (a[h2].type == 3) ans[a[h2].val] += tree.ask(a[h2].y);
t[++k] = a[h2++];
}
}
while (h1 <= mid)
{
if (a[h1].type == 1) tree.add(a[h1].y,a[h1].val);
t[++k] = a[h1++];
}
while (h2 <= r)
{
if (a[h2].type == 2) ans[a[h2].val] -= tree.ask(a[h2].y);
if (a[h2].type == 3) ans[a[h2].val] += tree.ask(a[h2].y);
t[++k] = a[h2++];
}
for (int i=l;i<=mid;++i)
if (a[i].type == 1) tree.add(a[i].y,-a[i].val);
//将加上的减回
for (int i=l;i<=r;++i) a[i] = t[i];
return;
}
int main(void)
{
freopen("mokia.in","r",stdin);
freopen("mokia.out","w",stdout);
int tot = 0, cnt = 0;
n = read();
while (true)
{
int type = read();
if (type == 3) break;
tot ++;
int x = read();
int y = read();
if (type == 1)
{
int v = read();
add(type, x, y, v);
}
if (type == 2)
{
int xx = read();
int yy = read();
xx ^= x ^= xx ^= x;
yy ^= y ^= yy ^= y;
add(3, x, y, ++cnt);
add(2, xx-1, y, cnt);
add(2, x, yy-1, cnt);
add(3, xx-1, yy-1, cnt);
//2表示减法 3表示加法
}
}
cdq(1, m);
for (int i=1;i<=cnt;++i) printf("%d\n",ans[i]);
return 0;
}