题意: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
我想了一会儿,觉得最外层按x排比较科学,内部对t进行划分排序(相当于快排,将t值<=mid的划分到左边,同时对于划分到同一侧的点要保证原来的相对顺序不变),对y用树状数组来维护。每个节点[L,R]划分出来是这样的:
要找[L,mid]对[mid+1,R]的贡献:
先考虑对lda的贡献。枚举t∈[mid+1,R]的点(t0,x0,y0),区间内的点由于已经按时间划分好了,所以不需要考虑t
再考虑对rxiao的贡献:类似地,找出[L,mid]中x值大于[mid+1,R]中的x值的即可。
注意一层分治并不能找出[mid+1,R]中所有值的lda和rxiao,但整个分治一定会不遗漏不重复地覆盖每个点的决策区间。每层复杂度nlogn,总共logn层,总复杂度nlog^2n。貌似有人说cdq分治可以做到nlogn?我觉得不太科学,毕竟三维,不可能把某一维直接吃掉吧。。
cdq分治做这种题真是优秀,空间复杂度仅为O(n),时间复杂度也不逊于高级数据结构,并且分治(对半分)以及树状数组的常数都是极小的,基本是严格logn。
#include
#include
#include
#include
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=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
#include
#include
#include
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
<三>线段树套平衡树(treap)。(43MB,5.1s)
其实我觉得线段树套平衡树比树状数组套主席树好想一些。lda和rxiao本质上就是求一个区间当中有多少数大于/小于一个给定的数,这是线段树套平衡树的基本功能。
空间复杂度是O(nlogn),这是比较能够接受的。理论时间复杂度是O(nlog^2n),但是这个常数很大。线段树多路查询的复杂度很难证明,我大致可以确信线段树一次多路查询极端上届不超过log^2n,实践表现均摊一般为4logn。treap就更不敢保证了。。
其实写树状数组套AVL会快很多。。
#include
#include
#include
#include
#include
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; isz = 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