[CQOI2011]动态逆序对【CDQ分治】

还是比较模板的题可是一开始没想出来。。之前电科校赛就遇到这道原题,今天终于补上了。。
主要的思路还是计算出每个数对前面和后面的影响,然后删除的时候用总的减去就好了。。
但是问题在于,对于删除操作怎么动态的维护前后关系:当删除一个数a[i]之后,要知道在1…i-1范围内有多少比a[i]大,在i+1…n中有多少比a[x]小的。
当然树套树可以做,但是常数和空间都比较大。这个时候就可以用CDQ分治了。。

我们可以把删除操作当做按时间t插入,对于一个数,它的贡献就是

(t’ < t && pos’ < pos && val’ > val )+ (t’ < t && pos’ > pos && val’ < val)的数量。

这个t相当于一个 查询贡献的优先等级 (对于一个逆序对,它属于两个数中哪一个贡献的?),结合题目,第一个插入的优先等级肯定要高,然后依次降低。对于没有删除的数,随便一个递减的组合都行。
每个数的贡献直接相加就是整体数组的逆序数。(如果没有t的制约关系对于一个逆序对的两个数都会算一次)减去每个数对数组的贡献就是删去这个位置后的数组逆序数。
这就转化为了CDQ的模板题了。。CDQ代码短,时间空间复杂度都很优秀,相比于树套树。。。。。
最后注意的是树状数组不要memset。。

#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e6+5;
typedef long long ll;
const ll mod = 1e9+7;
int Case = 1;
int n, m;
struct Tree{
    ll c[maxn];int up;
    int lowbit(int x) {
        return x&(-x);
    }
    void update(int pos, int val) {
        for(int i = pos; i <= up; i += lowbit(i)) {
            c[i] += val;
        }
    }
    ll query(int pos) {
        ll res = 0;
        for(int i = pos; i >= 1; i -= lowbit(i)) {
            res += c[i];
        }
        return res;
    }
}tree;
int cc[maxn];
struct node{
    int x, y, z;
    int flag;
}ss[maxn], temp[maxn];
bool cmp1(node a, node b) {
    if(a.x == b.x) return a.y < b.y;
    return a.x < b.x;
}
bool cmp2(node a, node b) {
    return a.y < b.y;
}
int id[maxn];
ll res[maxn];
void cdq(int l, int r) {
    if(l == r) return;
    int mid = (l+r)/2;
    cdq(l, mid);cdq(mid+1, r);
    int cnt = 0;
    for(int i = l; i <= mid; i++) {
        temp[++cnt] = ss[i];temp[cnt].flag = 1;
    }
    for(int i = mid+1; i <= r; i++) {
        temp[++cnt] = ss[i];temp[cnt].flag = 0;
    }
    sort(temp+1, temp+1+cnt, cmp2);
    for(int i = cnt; i >= 1; i--) {
        if(temp[i].flag == 0) {
            res[temp[i].x] += tree.query(temp[i].z);
        }
        else tree.update(temp[i].z, 1);
    }
    for(int i = 1; i <= cnt; i++) if(temp[i].flag) tree.update(temp[i].z, -1);
    for(int i = 1; i <= cnt; i++) {
        if(temp[i].flag == 0) {
            res[temp[i].x] += tree.query(n) - tree.query(temp[i].z);
        }
        else tree.update(temp[i].z, 1);
    }
    for(int i = 1; i <= cnt; i++) if(temp[i].flag) tree.update(temp[i].z, -1);
}
void solve() {
    scanf("%d%d", &n, &m);tree.up = n;
    for(int i = 1; i <= n; i++) scanf("%d", &cc[i]), id[cc[i]] = i, ss[i].y = i, ss[i].z = cc[i], ss[i].flag = 0;
    int cnt = n;
    for(int i = 1; i <= m; i++) {
        int x;scanf("%d", &x);
        int p = id[x];
        ss[p].x = cnt--;
        ss[p].flag = 1;
    }
    for(int i = 1; i <= n; i++) {
        if(!ss[i].flag) ss[i].x = --cnt;
    }
    sort(ss+1, ss+1+n, cmp1);
    cdq(1, n);
    ll sum = 0;
    for(int i = 1; i <= n; i++) sum += res[i];
    for(int i = n; i >= n-m+1; i--) {
        printf("%lld\n", sum);sum -= res[i];
    }
    return;
}
int main() {
    //g++ -std=c++11 -o2 1.cpp -o f && ./f < in.txt
    //ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
    //freopen("in.txt", "r", stdin);
    //freopen("out.txt","w",stdout);
#endif
while(Case--) {
    solve();
    }
return 0;
}

另外CDQ的代码也可以这样写,感觉这样比较正宗。。。。

void cdq(int l, int r) {
    if(l == r) return;
    int mid = (l+r)/2;
    cdq(l, mid);cdq(mid+1, r);
    sort(ss+l, ss+mid+1, cmp2);
    sort(ss+mid+1, ss+r+1, cmp2);
    int ii = l, jj = mid+1;
    while(jj <= r) {
        while(ss[ii].y < ss[jj].y && ii <= mid) tree.update(ss[ii++].z, 1);
        res[ss[jj].x] += tree.query(n)-tree.query(ss[jj].z);
        jj++;
    }
    for(int i = l; i < ii; i++) tree.update(ss[i].z, -1);
    ii = mid, jj = r;
    while(jj >= mid+1) {
        while(ss[ii].y >= ss[jj].y && ii >= l) tree.update(ss[ii--].z, 1);
        res[ss[jj].x] += tree.query(ss[jj].z);
        jj--;
    }
    for(int i = mid; i > ii; i--) tree.update(ss[i].z, -1);
}

猜你喜欢

转载自blog.csdn.net/qq_39921637/article/details/96470002