动态逆序对两种做法

P3157 [CQOI2011] 动态逆序对
对于序列 a a a,它的逆序对数定义为集合 { ( i , j ) ∣ i < j ∧ a i > a j } \{(i,j)| i<j \wedge a_i > a_j \} {(i,j)i<jai>aj}中的元素个数。
现在给出 1 ∼ n 1\sim n 1n 的一个排列,按照某种顺序依次删除 m m m 个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数。 1 ≤ n ≤ 1 0 5 1\le n \le 10^5 1n105 1 ≤ m ≤ 50000 1\le m \le 50000 1m50000

CDQ分治

离线
时间 O ( n log ⁡ 2 n ) O(n\log^2 n) O(nlog2n)
空间 O ( n ) O(n) O(n)

#include<bits/stdc++.h>
using namespace std;
#define ll long long 
#define P pair<ll,ll>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 1e5 + 10;
struct node {
    int a, b, c;
}p[N], temp[N];
ll n, m, pos[N], c[N], ans[N];
int lowbit(int x) {
    return x & -x;
}
void add(int x, int k) {
    for (; x <= n; x += lowbit(x)) c[x] += k;
}
ll ask(int x) {
    ll res = 0;
    for (; x; x -= lowbit(x)) res += c[x];
    return res;
}
void mergesort(int l, int r) {
    if (l >= r) return;
    int mid = l + r >> 1;
    mergesort(l, mid), mergesort(mid + 1, r);
    int i = l, j = mid + 1, cnt = 0; //计算i.a<j.a,i.b>j.b,i.c>j.c
    while (i <= mid && j <= r) {
        if (p[i].b > p[j].b) add(p[i].c, 1), temp[++cnt] = p[i++];
        else  ans[p[j].c] += ask(n) - ask(p[j].c - 1), temp[++cnt] = p[j++];
    }
    while (i <= mid) add(p[i].c, 1), temp[++cnt] = p[i++];
    while (j <= r) ans[p[j].c] += ask(n) - ask(p[j].c - 1), temp[++cnt] = p[j++];
    for (int i = l; i <= mid; ++i) add(p[i].c, -1);
    i = mid, j = r; //计算i.a>j.a,i.b<j.b,i.c>j.c
    while (i >= l && j >= mid + 1) {
        if (p[i].b > p[j].b) add(p[j].c, 1), j--;
        else ans[p[i].c] += ask(n) - ask(p[i].c - 1), i--;
    }
    while (i >= l) ans[p[i].c] += ask(n) - ask(p[i].c - 1), i--;
    while (j >= mid + 1) add(p[j].c, 1), j--;
    for (int i = mid + 1; i <= r; ++i) add(p[i].c, -1);
    for (int i = l; i <= r; ++i) p[i] = temp[i - l + 1];
}
inline void solve() {
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) {
        int tmp;
        cin >> tmp;
        pos[tmp] = i;
        p[i] = { i,tmp,0 };
    }
    for (int i = 1; i <= m; ++i) {
        int tmp;
        cin >> tmp;
        p[pos[tmp]].c = i;//删除时间,删除时间晚贡献逆序对
    }
    int now = n;
    for (int i = 1; i <= n; ++i) {
        if (p[i].c == 0) p[i].c = now--;
    }
    mergesort(1, n);//i.a<j.a,i.b>j.b,i.c>j.c或i.a>j.a,i.b<j.b,i.c>j.c
    for (int i = n - 1; i >= 1; --i) ans[i] += ans[i + 1];//ans为每个时间点产生逆序对,需要后缀和
    for (int i = 1; i <= m; ++i) cout << ans[i] << '\n';
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int _t = 1;
    //cin >> _t;
    while (_t--) {
        solve();
    }
    return 0;
}

树状数组套线段树

在线
时间 O ( n log ⁡ 2 n ) O(n\log^2 n) O(nlog2n)
空间 O ( n log ⁡ 2 n ) O(n\log^2 n) O(nlog2n)

#include<bits/stdc++.h>
using namespace std;
#define ll long long 
#define P pair<ll,ll>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 1e5 + 10;
struct tree {
    int l, r, sum;
}tr[300 * N];
int n, m, pos[N], rt[N], tot, qa[N], qb[N];
int lowbit(int x) {
    return x & -x;
}
void modify(int& x, int l, int r, int p, int k) {
    if (!x) x = ++tot;
    tr[x].sum += k;
    if (l == r) return;
    int mid = l + r >> 1;
    if (p <= mid) modify(tr[x].l, l, mid, p, k);
    else modify(tr[x].r, mid + 1, r, p, k);
}
int query(int l, int r, int x, int op) {//找[l,r]比x小/大的个数,op=0大于
    int cnta = 0, cntb = 0, res = 0;
    for (int i = l - 1; i; i -= lowbit(i)) qa[++cnta] = rt[i];
    for (int i = r; i; i -= lowbit(i)) qb[++cntb] = rt[i];
    l = 1, r = n;
    while (l != r) {
        int mid = l + r >> 1;
        if (x <= mid) {
            if (!op) {//右子树都比x大
                for (int i = 1; i <= cnta; ++i) res -= tr[tr[qa[i]].r].sum;
                for (int i = 1; i <= cntb; ++i) res += tr[tr[qb[i]].r].sum;
            }
            for (int i = 1; i <= cnta; ++i) qa[i] = tr[qa[i]].l;//遍历左子树
            for (int i = 1; i <= cntb; ++i) qb[i] = tr[qb[i]].l;
            r = mid;
        }
        else {
            if (op) {//左子树都比x小
                for (int i = 1; i <= cnta; ++i) res -= tr[tr[qa[i]].l].sum;
                for (int i = 1; i <= cntb; ++i) res += tr[tr[qb[i]].l].sum;
            }
            for (int i = 1; i <= cnta; ++i) qa[i] = tr[qa[i]].r;//遍历右子树
            for (int i = 1; i <= cntb; ++i) qb[i] = tr[qb[i]].r;
            l = mid + 1;
        }
    }
    return res;
}
inline void solve() {
    ll ans = 0;
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) {
        int x;
        cin >> x;
        pos[x] = i;
        ans += query(1, i - 1, x, 0);
        for (int j = i; j <= n; j += lowbit(j)) modify(rt[j], 1, n, x, 1);//树状数组每一点建立一棵权值线段树
    }
    cout << ans << '\n';
    for (int i = 1; i < m; ++i) {
        int x;
        cin >> x;
        ans -= query(1, pos[x] - 1, x, 0);//减去前面比x大的个数
        ans -= query(pos[x] + 1, n, x, 1);//减去后面比x小的个数
        for (int j = pos[x]; j <= n; j += lowbit(j)) modify(rt[j], 1, n, x, -1);
        cout << ans << '\n';
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int _t = 1;
    //cin >> _t;
    while (_t--) {
        solve();
    }
    return 0;
}

习题

破风阵型
众所周知……
​ zxt 不仅是 cugbacm 的队长,还是一个公路车(一种自行车)大佬。为了迎战新一届的环法自行车赛,zxt 组建了一支有 n 名选手的队伍,并且将进行一次持续 m 天的集训。
​ 在进行团队骑行的时候,几乎所有的职业队伍都会选择 “开火车”,也就是所有人排成一列行驶,前面的人为后面的人承受风阻,后面的人为前面的人吸收尾流,这样虽然最前面和最后面的人会很累,但是中间的选手可以最大程度上的保存体力,我们管这种阵型叫做破风阵型。为了不让最前面和最后面的人掉队,在一场比赛中,整个队伍会不断调整排列顺序,让所有人都能得到喘息的机会。
​ zxt 的队伍作为夺冠热门,自然也会采用这种策略。但是 zxt 队伍里面的选手水平、身高各不相同,所以不同的排列方法也会导致整个队伍的骑行效果不同。现在 zxt 给队伍里面所有的人一个排名(表示水平高低,排名越高实力越强,不存在并列的情况),和一个身高值。在这 m 天的集训中,队伍每天都会以一种排列进行骑行训练,但是每当结束了一天的训练,zxt 都会交换两个人的位置来尝试不同的排列。
​ 然后 zxt 决定用破风值来描述某一种排列的效果,假如在阵型里,选手 A 在选手 B 前面,并且选手 A 的实力不如选手 B,那么他们就会为整个阵型贡献他们身高值之和的破风值。
现在请你说出这 m 天每一天的排列的破风值。结果对 10e9 +7 取模。5e4

树状数组套线段树,这个线段树需要动态开点,树状数组维护的是选手当前的位置,然后树状数组每个节点都开一个权值线段树,维护这个节点范围内选手排名的情况。
• 如果交换第 x 和第 y 名选手(这里认为 x < y ),那么只需要考虑 [x + 1, y − 1] 里的元素对答案的影响就可以。
• 就是减去在这个范围里小于 bx 的元素和大于 by 的元素,以及加上大于 bx 和小于 by 的元素的影响。
• 需要注意取模和空间大小,可以开一个栈把不用的节点存起来下次用。
• cdq 分治,跟动态逆序对的做法几乎是一样的,把每次操作都拆成三个限制构成的点,之后就是经典的 cdq 操作了。

#include <bits/stdc++.h>
#define lowbit(i) i & (-i)
using namespace std;
typedef long long ll;
const int size_tree = 1e7 + 5;
const int MOD = 1e9 + 7;
const int maxn = 5e4 + 5;
int n, m, pos[maxn], rt[maxn], cnt;
ll ans, value[maxn], qs, qsz;
struct tree{
    int L, R, num;
    ll sum;
} t[size_tree];
void update(int& x, int l, int r, int k, int v, int v1) {
    if (!x) x = ++cnt;
    if (l == r){
        t[x].num += v1;
        t[x].sum += v * v1;
        return;
    }
    int mid = (l + r) >> 1;
    if (k <= mid) update(t[x].L, l, mid, k, v, v1);
    else update(t[x].R, mid + 1, r, k, v, v1);
    t[x].sum = (t[t[x].L].sum + t[t[x].R].sum) % MOD;
    t[x].num = t[t[x].L].num + t[t[x].R].num;
    return;
}
void add(int x, int fg) {
    int p = pos[x], v = value[x];
    for (; x <= n; x += lowbit(x)) update(rt[x], 1, n, p, v, fg);
}
void query(int x, int l, int r, int l1, int r1, int fg) {
    if (l >= l1 && r <= r1) return qsz += fg * t[x].num, (qs += fg * t[x].sum) %= MOD, void();
    int mid = (l + r) >> 1;
    if (l1 <= mid) query(t[x].L, l, mid, l1, r1, fg);
    if (r1 > mid) query(t[x].R, mid + 1, r, l1, r1, fg);
}
void ask(int l, int r, int l1, int r1) {
    qsz = qs = 0;
    if (l > r || l1 > r1) return;
    for (; r; r -= lowbit(r)) query(rt[r], 1, n, l1, r1, 1);
    --l;
    if (l) {
        for (; l; l -= lowbit(l)) query(rt[l], 1, n, l1, r1, -1);
    }
}
int f(int x){
    return (qs + 1ll * qsz * value[x] % MOD) % MOD;
}
int main(){
    //ios::sync_with_stdio(false);
    //cin.tie(0);
    //cout.tie(0);
    cin >> n >> m;
    for (int i = 1; i <= n; i++){
        cin >> pos[i] >> value[i];
        add(i, 1);
        ask(1, i - 1, pos[i] + 1, n);
        ans += f(i);
        ans %= MOD;
    }
    cout << ans << '\n';
    for (int i = 1; i <= m - 1; i++){
        int l, r;
        cin >> l >> r;
        if (l > r) swap(l, r);
        if (l != r){
            ask(l + 1, r - 1, 1, pos[l] - 1);
            ans -= f(l);
            ans %= MOD;
            ask(l + 1, r - 1, pos[l] + 1, n);
            ans += f(l);
            ans %= MOD;
            ask(l + 1, r - 1, 1, pos[r] - 1);
            ans += f(r);
            ans %= MOD;
            ask(l + 1, r - 1, pos[r] + 1, n);
            ans -= f(r);
            ans %= MOD;
            pos[l] > pos[r] ? ans -= value[l] + value[r] : ans += value[l] + value[r];
            ans %= MOD;
            add(l, -1), add(r, -1);
            swap(pos[l], pos[r]), swap(value[l], value[r]);
            add(l, 1), add(r, 1);
        }
        if (ans < 0) ans += MOD;
        cout << ans << '\n';
    }
    return 0;
}

错误代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long 
#define P pair<ll,ll>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 1e5 + 10;
struct node {
    ll a, b;
}p[N];
struct tree {
    ll l, r, cnt, sum;
}tr[300 * N];
ll n, m, pos[N], rt[N], tot, qa[N], qb[N];
ll lowbit(ll x) {
    return x & -x;
}
void modify(ll& x, ll l, ll r, ll p, ll k1, ll k2) {
    if (!x) x = ++tot;
    tr[x].cnt += k1;
    tr[x].sum += k2;
    if (l == r) return;
    ll mid = l + r >> 1;
    if (p <= mid) modify(tr[x].l, l, mid, p, k1, k2);
    else modify(tr[x].r, mid + 1, r, p, k1, k2);
}
ll query(ll l, ll r, ll x, ll op) {//找[l,r]比x小/大的个数,op=0大于
    ll cnta = 0, cntb = 0, res = 0;
    for (ll i = l - 1; i; i -= lowbit(i)) qa[++cnta] = rt[i];
    for (ll i = r; i; i -= lowbit(i)) qb[++cntb] = rt[i];
    l = 1, r = n;
    while (l != r) {
        ll mid = l + r >> 1;
        if (p[x].a <= mid) {
            if (!op) {//右子树都比x大
                for (ll i = 1; i <= cnta; ++i) res -= tr[tr[qa[i]].r].cnt * p[x].b + tr[tr[qa[i]].r].sum;
                for (ll i = 1; i <= cntb; ++i) res += tr[tr[qb[i]].r].cnt * p[x].b + tr[tr[qb[i]].r].sum;
            }
            for (ll i = 1; i <= cnta; ++i) qa[i] = tr[qa[i]].l;//遍历左子树
            for (ll i = 1; i <= cntb; ++i) qb[i] = tr[qb[i]].l;
            r = mid;
        }
        else {
            if (op) {//左子树都比x小
                for (ll i = 1; i <= cnta; ++i) res -= tr[tr[qa[i]].l].cnt * p[x].b + tr[tr[qa[i]].l].sum;
                for (ll i = 1; i <= cntb; ++i) res += tr[tr[qb[i]].l].cnt * p[x].b + tr[tr[qb[i]].l].sum;
            }
            for (ll i = 1; i <= cnta; ++i) qa[i] = tr[qa[i]].r;//遍历右子树
            for (ll i = 1; i <= cntb; ++i) qb[i] = tr[qb[i]].r;
            l = mid + 1;
        }
    }
    return res;
}
inline void solve() {
    ll ans = 0;
    cin >> n >> m;
    for (ll i = 1; i <= n; ++i) {
        cin >> p[i].a >> p[i].b;
        ans += query(1, i - 1, p[i].a, 0);
        for (ll j = i; j <= n; j += lowbit(j)) modify(rt[j], 1, n, p[i].a, 1, p[i].b);//树状数组每一点建立一棵权值线段树
    }
    cout << ans % mod << '\n';
    for (ll i = 1; i < m; ++i) {
        ll x, y;
        cin >> x >> y;
        ans -= query(x + 1, y - 1, x, 1);//减去[x,y]比x小的数的贡献
        ans -= query(x + 1, y - 1, y, 0);//减去[x,y]比y大的数的贡献
        ans += query(x + 1, y - 1, x, 0);//加上[x,y]比x大的数的贡献
        ans += query(x + 1, y - 1, y, 1);//加上[x,y]比y小的数的贡献
        if (p[x].a > p[y].a) ans -= (p[x].b + p[y].b);
        else if (p[x].a < p[y].a) ans += (p[x].b + p[y].b);
        for (ll j = x; j <= n; j += lowbit(j)) modify(rt[j], 1, n, p[x].a, -1, -p[x].b);
        for (ll j = x; j <= n; j += lowbit(j)) modify(rt[j], 1, n, p[y].a, 1, p[y].b);
        for (ll j = y; j <= n; j += lowbit(j)) modify(rt[j], 1, n, p[y].a, -1, -p[y].b);
        for (ll j = y; j <= n; j += lowbit(j)) modify(rt[j], 1, n, p[x].a, 1, p[x].b);
        swap(p[x], p[y]);
        cout << ans % mod << '\n';
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    ll _t = 1;
    //cin >> _t;
    while (_t--) {
        solve();
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值