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<j∧ai>aj}中的元素个数。
现在给出
1
∼
n
1\sim n
1∼n 的一个排列,按照某种顺序依次删除
m
m
m 个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数。
1
≤
n
≤
1
0
5
1\le n \le 10^5
1≤n≤105,
1
≤
m
≤
50000
1\le m \le 50000
1≤m≤50000。
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;
}