题意:N个数的排列,M次操作,每次求当前的逆序对数量并删掉一个数。
<一>先说一下cdq分治做法。(5960kb,1.4s)
网上很多题解,我都看不懂(其实很多人的程序几乎是一样的,就改了一下变量名),然后就自己硬着头皮想了这道题,基本是独立做出来的,做出来之后竟然1A,简直愉快。不过我太辣鸡了想了半天才发现这本质上是一个三维偏序,分别是时间,下标,数值,记为(t,x,y)。
我们可以把删除的过程倒过来,当做插入来做,时间t表示这个数是第几个插入的,显然给出的删除的点的t值依次是N,N-1,N-2...(越先删除的视为越后插入的)注意不在询问范围内的点的t值可以任意设置,并且显然没有哪两个点有相同的t或x或y值,这使得问题好考虑得多了。我们求的就是按顺序插入每一个数时,这个数左边比它大的、右边比它小的分别有多少个。形式化地,对一个点(t0,x0,y0),求出满足t<t0,x<x0,y>y0的点的个数记为lda[t0],满足t<t0,x>x0,y>y0的点的个数记为rxiao[t0]。
我想了一会儿,觉得最外层按x排比较科学,内部对t进行划分排序(相当于快排,将t值<=mid的划分到左边,同时对于划分到同一侧的点要保证原来的相对顺序不变),对y用树状数组来维护。每个节点[L,R]划分出来是这样的:
要找[L,mid]对[mid+1,R]的贡献:
先考虑对lda的贡献。枚举t∈[mid+1,R]的点(t0,x0,y0),区间内的点由于已经按时间划分好了,所以不需要考虑t<t0这一条件。只需找出左区间中x<x0且y>y0的点,由于两边的x值各自保持单调(如图),所以可以像树状数组求逆序对一样,将[L,mid]区间内的点的y值在树状数组上增加1,然后求[mid+1,R]的每个y值在树状数组上的前缀和即可。由于l1和l2都是单增的,这一操作复杂度为nlogn。
再考虑对rxiao的贡献:类似地,找出[L,mid]中x值大于[mid+1,R]中的x值的即可。
注意一层分治并不能找出[mid+1,R]中所有值的lda和rxiao,但整个分治一定会不遗漏不重复地覆盖每个点的决策区间。每层复杂度nlogn,总共logn层,总复杂度nlog^2n。貌似有人说cdq分治可以做到nlogn?我觉得不太科学,毕竟三维,不可能把某一维直接吃掉吧。。
cdq分治做这种题真是优秀,空间复杂度仅为O(n),时间复杂度也不逊于高级数据结构,并且分治(对半分)以及树状数组的常数都是极小的,基本是严格logn。
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring> using namespace std; const int MAXN = 100010; #define rep(i,a,b) for(int i=a;i<=b;++i) #define erp(i,a,b) for(int i=a;i>=b;--i) #define clr(a) memset(a,0,sizeof a) #define LL long long void get(int &r) { char c; r=0; do c=getchar(); while (c<'0'||c>'9'); do r=r*10+c-'0', c=getchar(); while (c>='0'&&c<='9'); } int N, M; namespace BIT { int c[MAXN], r; void add(int i, int x) { for (; i<=N; i+=i&-i) c[i] += x; } int sum(int i) { r = 0; for (; i>0; i-=i&-i) r += c[i]; return r; } }; struct dot { int t, x, y; // every t, x, y is unique } a[MAXN], np[MAXN]; int rxiao[MAXN], lda[MAXN]; LL ans[MAXN]; void cdq(int L, int R) { using namespace BIT; if (L >= R) return; int mid = (L+R)>>1; int l1 = L, l2 = mid+1; rep(i, L, R) { if (a[i].t <= mid) np[l1++] = a[i]; else np[l2++] = a[i]; } rep(i, L, R) a[i] = np[i]; l1 = L; rep(i, mid+1, R) // 先找坐标更小,值更大的 { for (; l1<=mid && np[l1].x<np[i].x; ++l1) add(np[l1].y, 1); lda[ np[i].t ] += (l1 - L) - sum(np[i].y); //注意是+= } rep(i, L, l1-1) add(np[i].y, -1); //将树状数组清零 l1 = mid; erp(i, R, mid+1) // 再找坐标更大,值更小的 { for (; l1>=L && np[l1].x>np[i].x; --l1) add(np[l1].y, 1); rxiao[ np[i].t ] += sum(np[i].y - 1); } rep(i, l1+1, mid) add(np[i].y, -1); cdq(L, mid), cdq(mid+1, R); } int pos[MAXN]; int main() { get(N), get(M); rep(i, 1, N) get(a[i].y), a[i].x = i, pos[a[i].y] = i; int t, tmr = N; rep(i, 1, M) get(t), a[pos[t]].t = tmr--; rep(i, 1, N) if (!a[i].t) a[i].t = tmr--; cdq(1, N); rep(i, 1, N) ans[i] = ans[i-1] + rxiao[i] + lda[i]; erp(i, N, N-M+1) cout << ans[i] << '\n'; return 0; }
由于这篇blog的重点是cdq分治,高级数据结构的做法是以前写的,但这道题确实是个好题,我还是简单说一下。
<二>树状数组套主席树。(103MB,2.3s)
非常裸,主席树(按权值建)表示前面一段区间中的每个值的数量,方便求lda和rxiao(上面提过这两个的含义)。因为需要修改,要用树状数组来绑定前缀和。由于主席树的强大,删点和插点两种顺序都能做。
注意树状数组套主席树的空间复杂度是O((n+m)log^2n)的,理论上128M开不下。有个优化,一开始不保存在主席树里面,用树状数组来保存,然后每次删除转换为增加一个点。这样空间复杂度是O(mlog^2),由于maxm<=maxn/2,可以节约不少。还有,建议用数组来保存,因为OJ上指针的内存开销是整数的两倍。
时间当然常数很小了,由于线段树是走单路且无tag,是严格logn的,树状数组更小了。
#include<stdio.h> #include<cstring> #include<algorithm> #include<iostream> using namespace std; #define LL long long const int MAXN = 100010; void get(int &r) { char c; r=0; do c=getchar(); while (c<'0'||c>'9'); do r=r*10+c-'0', c=getchar(); while (c>='0'&&c<='9'); } int N, M, n; int a[MAXN], b[MAXN]; LL ans; namespace pre { int bt[MAXN]; void update(int x, int d) { for (; x<=N; x+=x&-x) bt[x] += d; } int gsum(int x) { int res = 0; for (; x>0; x-=x&-x) res += bt[x]; return res; } }; int lda[MAXN], rxiao[MAXN]; struct Node { int cnt; Node*lch, *rch; }; struct SegTree { Node nil, *NIL; Node nds[MAXN*85], *ncnt; Node *rt[MAXN]; Node *c[50]; int cn; SegTree () { NIL = &nil; rt[0] = nil.lch = nil.rch = NIL; ncnt = nds; } void ins(Node*&x, Node*p, int v, int dt, int l, int r) { if (!p) p = NIL; x = ++ncnt; x->cnt = p->cnt + dt; x->lch = p->lch; x->rch = p->rch; if (l == r) return; int mid = (l+r)>>1; if (v <= mid) ins(x->lch, p->lch, v, dt, l, mid); else ins(x->rch, p->rch, v, dt, mid+1, r); } void load(int pos) { for (cn = 0; pos; pos -= pos&-pos) c[++cn] = rt[pos]; } int calc_greater(Node*x, int v, int l=1, int r=N) { if (l == r) return 0; int mid = (l+r) >> 1, res = 0; if (v > mid) { for (int i = 1; i<=cn; ++i) c[i] = c[i]->rch; return calc_greater(x->rch, v, mid+1, r); } for (int i = 1; i<=cn; ++i) res += c[i]->rch->cnt, c[i] = c[i]->lch; return res + calc_greater(x->lch, v, l, mid); } int calc_less(Node*x, int v, int l=1, int r=N) { if (l == r) return 0; int mid = (l+r) >> 1, res = 0; if (v <= mid) { for (int i = 1; i<=cn; ++i) c[i] = c[i]->lch; return calc_less(x->lch, v, l, mid); } for (int i = 1; i<=cn; ++i) res += c[i]->lch->cnt, c[i] = c[i]->rch; return res + calc_less(x->rch, v, mid+1, r); } void update(int i, int v, int dt) { for (; i<=N; i += i&-i) ins(rt[i], rt[i], v, dt, 1, N); } LL erase(int v) { int p = b[v], cnt; load(N); cnt = calc_less(rt[N], v); load(p); cnt -= calc_less(rt[p], v); load(p-1); cnt += calc_greater(rt[p-1], v); update(p, v, 1); return ans = ans - (lda[p] + rxiao[p] - cnt); } } sg; int ba[MAXN]; int main() { get(N), get(M); for (int i = 1; i<=N; ++i) get(a[i]), b[a[i]] = i; for (int i = 1; i<=N; ++i) { sg.rt[i] = sg.NIL; sg.rt[i]->lch = sg.rt[i]->rch = sg.NIL; lda[i] = (i-1) - pre::gsum(a[i]); pre::update(a[i], 1); ans += lda[i]; } memset(pre::bt, 0, sizeof pre::bt); for (int i = N; i>=1; --i) { rxiao[i] = pre::gsum(a[i]); pre::update(a[i], 1); } cout << ans << '\n'; for (int i = 1, x; i<M; ++i) { get(x); cout << sg.erase(x) << '\n'; } return 0; }
<三>线段树套平衡树(treap)。(43MB,5.1s)
其实我觉得线段树套平衡树比树状数组套主席树好想一些。lda和rxiao本质上就是求一个区间当中有多少数大于/小于一个给定的数,这是线段树套平衡树的基本功能。
空间复杂度是O(nlogn),这是比较能够接受的。理论时间复杂度是O(nlog^2n),但是这个常数很大。线段树多路查询的复杂度很难证明,我大致可以确信线段树一次多路查询极端上届不超过log^2n,实践表现均摊一般为4logn。treap就更不敢保证了。。
其实写树状数组套AVL会快很多。。
#include<cstdio> #include<algorithm> #include<iostream> #include<cstdlib> #include<cstring> using namespace std; #define idx(l,r) ((l + r) | (l != r)) #define LL long long const int MAXN = 100010; int N, M; int a[MAXN], pos[MAXN]; LL ans; int bt[MAXN]; void update(int x, int d) { for (; x<=N; x+=x&-x) bt[x] += d; } int qsum(int x) { int res = 0; for (; x>0; x-=x&-x) res += bt[x]; return res; } int lda[MAXN], rxiao[MAXN]; struct Node { int rn, v, sz; Node*ch[2]; } nil, *NIL = &nil; struct Treap { Node nd[MAXN * 20], *ncnt; Node *rt[MAXN * 2]; Treap () { ncnt = nd; nil.ch[0] = nil.ch[1] = NIL; for (int i = 0; i<MAXN*2; ++i) rt[i] = NIL; } inline void pushup(Node*x) { x->sz = x->ch[0]->sz + x->ch[1]->sz + 1; } inline void rotate(Node*&x, bool d) //d=0左旋 { Node*y = x->ch[!d]; x->ch[!d] = y->ch[d]; y->ch[d] = x; pushup(x), pushup(x = y); } void ins(Node*&x, int v) { if (x == NIL) { x = ++ncnt; x->ch[0] = x->ch[1] = NIL; x->v = v, x->rn = rand(); } else { bool d = v > x->v; ins(x->ch[d], v); if (x->rn < x->ch[d]->rn) rotate(x, !d); } pushup(x); } int qmore(Node*x, int v) { if (x==NIL) return 0; if (v > x->v) return qmore(x->ch[1], v); if (v == x->v) return x->ch[1]->sz + 1; return x->ch[1]->sz + 1 + qmore(x->ch[0], v); } int qless(Node*x, int v) { if (x==NIL) return 0; if (v < x->v) return qless(x->ch[0], v); if (v == x->v) return x->ch[0]->sz + 1; return x->ch[0]->sz + 1 + qless(x->ch[1], v); } } tp; int qmore(int v, int l, int r, int L=1, int R=N) { if (R < l || L > r) return 0; if (L>=l && R<=r) return tp.qmore(tp.rt[idx(L, R)], v); int mid = (L + R) >> 1; return qmore(v, l, r, L, mid) + qmore(v, l, r, mid+1, R); } int qless(int v, int l, int r, int L=1, int R=N) { if (R < l || L > r) return 0; if (L>=l && R<=r) return tp.qless(tp.rt[idx(L, R)], v); int mid = (L + R) >> 1; return qless(v, l, r, L, mid) + qless(v, l, r, mid+1, R); } void ins(int i, int v, int L=1, int R=N) { tp.ins(tp.rt[idx(L, R)], v); if (L == R) return; int mid = (L + R) >> 1; if (i <= mid) ins(i, v, L, mid); else ins(i, v, mid+1, R); } int main() { srand(45251); scanf("%d%d", &N, &M); for (int i = 1; i<=N; ++i) { scanf("%d", a+i), pos[a[i]] = i; lda[i] = i-1-qsum(a[i]), update(a[i], 1); ans += lda[i]; } memset(bt, 0, sizeof bt); for (int i = N; i; --i) { rxiao[i] = qsum(a[i]); update(a[i], 1); } cout << ans << '\n'; for (int t, i = 1; i<M; ++i) { scanf("%d", &t); ans -= rxiao[pos[t]] - qless(t, pos[t]+1, N); ans -= lda[pos[t]] - qmore(t, 1, pos[t]-1); cout << ans << '\n'; ins(pos[t], t); } return 0; }