还是比较模板的题可是一开始没想出来。。之前电科校赛就遇到这道原题,今天终于补上了。。
主要的思路还是计算出每个数对前面和后面的影响,然后删除的时候用总的减去就好了。。
但是问题在于,对于删除操作怎么动态的维护前后关系:当删除一个数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);
}