天天看点

翔哥的hu测 T3. 刁难老师(平衡树+线段树)tip

版权属于ZYXZYXZYX,想要引用此题(包括题面)的朋友请联系博主

翔哥的hu测 T3. 刁难老师(平衡树+线段树)tip
翔哥的hu测 T3. 刁难老师(平衡树+线段树)tip
翔哥的hu测 T3. 刁难老师(平衡树+线段树)tip

题目来源:[2016国家集训队互测] 基础排序算法练习题(金策)

原题提交地址

分析:

防AK毒瘤题啊。。。

1. 预备技能——消逆序对排序:O(q(m+n²)log n)

对于一个序列,找到其中一个位置 i i 满足a[i]>a[i+1]a[i]>a[i+1],交换 a[i] a [ i ] 和 a[i+1] a [ i + 1 ] ,重复这样的做法,直到不存在满足条件的 i i ,则序列被排好序。交换的次数等于逆序对的数量,而数量是n2n2级别的。

对于某一轮游戏,可以每次在待排序区间 [Li,Ri] [ L i , R i ] 内消逆序对。如果某个区间内的逆序对很少,那扫一遍区间是很浪费时间的。可以把满足条件的位置 i i 插入平衡树,对[Li,Ri][Li,Ri]这个区间进行消逆序对时只需要在平衡树中把 [Li,Ri) [ L i , R i ) 内的点依次取出,对序列修改后若产生新的满足条件的位置再插入即可。

其中平衡树可以使用 STL:set S T L : s e t ,只有 insert,erase,lowerbound i n s e r t , e r a s e , l o w e r b o u n d 三种操作。

在 m m 较大nn较小时, (m+n2)logn ( m + n 2 ) l o g n 的排序优于 mnlogn m n l o g n 的排序,但 q q 较大的时候依然会超时,只能通过3,4号点。

2. 转换——01序列

若要检验一个1−n1−n的排列能否被这 m m 次操作排好序,不需要对n!n!种排列进行检验,只需要对 2n 2 n 种 01 01 序列进行检验即可。这是排序网络的0-1原理,正确性很容易证明。

排序网络

3. 统计——逆向递推

下面研究具有什么性质的初始序列是可以被排序的。

先从01序列的特殊情况入手。一个最终被排好序的序列就是前面一段0,后面一段1,我们可以把 [Lm,Rm] [ L m , R m ] 这段中的数以任意方式打乱,得到的序列都是可通过第 m m 次操作排好序的序列。我们逆向递推,理论上就能得到每一次操作后能被以后的操作排好序的所有序列。

然而合法序列的个数是指数级的。

4. 精简——代表元素

若一个形如111000的序列能被正确排好序,那么101010必然也能被正确排好序。

我们需要一个对序列“好坏”程度的衡量标准,0越靠左,1越靠右,序列越接近升序,那么这个序列就越好。

因此容易发现,对区间[Li,Ri][Li,Ri]打乱顺序的最坏方式就是把1都放到左边,0都放到右边。对于一个序列 a a ,用pos(a,i)pos(a,i)表示 a a 中从左到右第ii个1所在的位置,若 a a 比bb优秀,当且仅当对于 i=1,2,…,k i = 1 , 2 , … , k 都有 pos(a,i)≥pos(b,i) p o s ( a , i ) ≥ p o s ( b , i ) 。

于是合法序列集的递推转化为了合法的最差元素的递推。

如果我们得到了能够被 m m 次操作排列有序的最差序列,那么任何一个比ta优秀的序列都可以通过这m此操作排列成有序的

5. 一步之遥——针对8号点的算法:O((m+n²)log n + qn)

8号点是01序列。先对序列使用消逆序对的方法排序(得到最差序列),然后对qq个询问每个 O(n) O ( n ) 检验。

6. 满分算法:O((m+n²+qn)log n)

不妨假设待排序的数组 A A 是一个nn排列。如果不是的话,可以将它先离散化,其中值相同的元素按照下标递增的顺序分配。容易看出这样并不会影响最终的答案。

现在尝试将01序列的算法推广到 n n 排列。对于初始排列A[1…n]A[1…n]和给定的 k k ,定义01序列BkBk,其中 Bk[i]=1 B k [ i ] = 1 当且仅当 A[i]≥k A [ i ] ≥ k 。

之前已经提到过,算法能将A数组正确排序,当且仅当对于所有 k=2,3,…,n k = 2 , 3 , … , n 该算法都能将01序列 Bk B k 正确排序。

根据刚才的算法,需要对每个 k k ,预处理出n−kn−k个0后跟 k k 个1的串倒着进行mm次操作后的结果 Ck C k 。

直接做肯定太慢了,我们可以做一件等价的事情:令初始排列为 1,2,…,n 1 , 2 , … , n ,按 i i 从mm到 1 1 的顺序,每次将[Li,Ri][Li,Ri]降序排序。(最差序列)

最后得到的序列中,将小于 k k 的值用0代替,其他用1代替,得到的01序列就是所求的CkCk,且 Ck C k 的某个位置的0改为1就可以得到 Ck−1 C k − 1 。

现在要对所有 k=2,3,…,n k = 2 , 3 , … , n 检查 Bk B k 是否比 Ck C k 优秀,即 Bk B k 的任何一个前缀的1都不能比 Ck C k 对应的前缀的1多。

如果我们把 Bk B k 中的1 取负号,在求完 k=i k = i 然后对 k=i−1 k = i − 1 求解时,就是加入一对新的 −1 − 1 和 +1 + 1 ,然后检查数组的每一个前缀和是否都非负,这个过程可以用线段树的区间加、维护区间最小值来实现(线段树维护前缀和序列)。

预处理过程依然用消逆序对的算法实现。

可通过100%的数据。

简单说一下我们的做法:

首先有一个递增序列 A A ,m个区间,我们把区间内的元素递减排列(这样就可以得到最差序列了),设为CC

(排序方法用 splay s p l a y 好了)

读入询问 B B 序列

枚举kk( k k 的范围就是数值的范围啦)

每次把BB和 C C 中>=k>=k的元素设为1, <k < k <script type="math/tex" id="MathJax-Element-80"> (其中,因为我们是顺序枚举k,每次只会改变序列中的一个值)

把 B B 中的1取负号

比较BB和 C C 的前缀和之和,如果所有的前缀和之和都>=0>=0,则 B B 序列比CC优秀,可以完成排序

(改变序列只以及维护前缀和之和都可以用线段树完成)

tip

一开始被卡了一段时间,结果发现是pushdown的位置写错了???大雾

void change(int bh,int l,int r,int L,int R,int z) {
    if (l>=L&&r<=R) {
        minn[bh]+=z;
        ad[bh]+=z;
        return;
    }
    push(bh);
    int mid=(l+r)>>;
    if (L<=mid) change(bh<<,l,mid,L,R,z);
    if (R>mid) change(bh<<|,mid+,r,L,R,z);
    minn[bh]=min(minn[bh<<],minn[bh<<|]);
}
           

以我的习惯,都是push都是在最前面的

mmp,看来要改码风了。。。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<set>

using namespace std;

const int M=;
const int N=;
int n,m,q;
int c[N],posc[N],b[N],posb[N];
int minn[N<<],ad[N<<];
struct node{
    int x,y;
};
node p[M];
set<int> s;
struct node2{
    int val,id;
};
node2 f[N];

void Sort(int l,int r) {            //将(l,r)降序排列 
    int i=*s.upper_bound(l-);      //大于某值的迭代器 
    while (l<=i&&i<r) {             //[l,r) 
        s.erase(i);
        swap(c[i],c[i+]);
        if (c[i+]<c[i+]&&c[i]>c[i+]) s.insert(i+);
        if (c[i-]<c[i]&&c[i-]>c[i+]) s.insert(i-);
        i=*s.upper_bound(l-); 
    }
}

void push(int bh) {
    if (!ad[bh]) return;
    int lc=bh<<;
    int rc=bh<<|;
    minn[lc]+=ad[bh]; ad[lc]+=ad[bh];
    minn[rc]+=ad[bh]; ad[rc]+=ad[bh];
    ad[bh]=;
}

void change(int bh,int l,int r,int L,int R,int z) {
    //push(bh);
    if (l>=L&&r<=R) {
        minn[bh]+=z;
        ad[bh]+=z;
        return;
    }
    push(bh);
    int mid=(l+r)>>;
    if (L<=mid) change(bh<<,l,mid,L,R,z);
    if (R>mid) change(bh<<|,mid+,r,L,R,z);
    minn[bh]=min(minn[bh<<],minn[bh<<|]);
}

int cmp(const node2 &a,const node2 &b) {
    return (a.val<b.val)||(a.val==b.val&&a.id<b.id);
}

int main()
{
    scanf("%d%d%d",&n,&m,&q);
    for (int i=;i<=m;i++) scanf("%d%d",&p[i].x,&p[i].y);
    for (int i=;i<=n;i++) c[i]=i;
    for (int i=;i<n;i++) s.insert(i);            //降序排列,逆序对的起点 
    for (int i=m;i>=;i--) Sort(p[i].x,p[i].y);  
    for (int i=;i<=n;i++) posc[c[i]]=i;          //c[i]的位置

    while (q--) {
        for (int i=;i<=n;i++) scanf("%d",&f[i].val),f[i].id=i;
        sort(f+,f++n,cmp);
        for (int i=;i<=n;i++) b[f[i].id]=i;      //离散化 
        for (int i=;i<=n;i++) posb[b[i]]=i;      //b[i]的位置

        memset(minn,,sizeof(minn));
        memset(ad,,sizeof(ad));
        bool flag=;
        for (int k=;k<=n;k++) {
            change(,,n,posb[k-],n,);          //b -1  k变大,k-1的位置变成了0 
            change(,,n,posc[k-],n,-);         //c 1   k变大,k-1的位置变成了0
            if (minn[]<) {flag=;break;}
        }
        if (flag) printf("AC\n");
        else printf("WA\n");
    }

    return ;
}