版权属于ZYXZYXZYX,想要引用此题(包括题面)的朋友请联系博主
题目来源:[2016国家集训队互测] 基础排序算法练习题(金策)
分析:
防AK毒瘤题啊。。。
1. 预备技能——消逆序对排序:O(q(m+n²)log n)
对于一个序列,找到其中一个位置
i
i
满足,交换
a[i]
a
[
i
]
和
a[i+1]
a
[
i
+
1
]
,重复这样的做法,直到不存在满足条件的
i
i
,则序列被排好序。交换的次数等于逆序对的数量,而数量是级别的。
对于某一轮游戏,可以每次在待排序区间
[Li,Ri]
[
L
i
,
R
i
]
内消逆序对。如果某个区间内的逆序对很少,那扫一遍区间是很浪费时间的。可以把满足条件的位置
i
i
插入平衡树,对这个区间进行消逆序对时只需要在平衡树中把
[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
较大较小时,
(m+n2)logn
(
m
+
n
2
)
l
o
g
n
的排序优于
mnlogn
m
n
l
o
g
n
的排序,但
q
q
较大的时候依然会超时,只能通过3,4号点。
2. 转换——01序列
若要检验一个的排列能否被这 m m 次操作排好序,不需要对种排列进行检验,只需要对 2n 2 n 种 01 01 序列进行检验即可。这是排序网络的0-1原理,正确性很容易证明。
3. 统计——逆向递推
下面研究具有什么性质的初始序列是可以被排序的。
先从01序列的特殊情况入手。一个最终被排好序的序列就是前面一段0,后面一段1,我们可以把
[Lm,Rm]
[
L
m
,
R
m
]
这段中的数以任意方式打乱,得到的序列都是可通过第
m
m
次操作排好序的序列。我们逆向递推,理论上就能得到每一次操作后能被以后的操作排好序的所有序列。
然而合法序列的个数是指数级的。
4. 精简——代表元素
若一个形如111000的序列能被正确排好序,那么101010必然也能被正确排好序。
我们需要一个对序列“好坏”程度的衡量标准,0越靠左,1越靠右,序列越接近升序,那么这个序列就越好。
因此容易发现,对区间打乱顺序的最坏方式就是把1都放到左边,0都放到右边。对于一个序列
a
a
,用表示
a
a
中从左到右第个1所在的位置,若
a
a
比优秀,当且仅当对于
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序列。先对序列使用消逆序对的方法排序(得到最差序列),然后对个询问每个 O(n) O ( n ) 检验。
6. 满分算法:O((m+n²+qn)log n)
不妨假设待排序的数组
A
A
是一个排列。如果不是的话,可以将它先离散化,其中值相同的元素按照下标递增的顺序分配。容易看出这样并不会影响最终的答案。
现在尝试将01序列的算法推广到
n
n
排列。对于初始排列和给定的
k
k
,定义01序列,其中
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
,预处理出个0后跟
k
k
个1的串倒着进行次操作后的结果
Ck
C
k
。
直接做肯定太慢了,我们可以做一件等价的事情:令初始排列为
1,2,…,n
1
,
2
,
…
,
n
,按
i
i
从到
1
1
的顺序,每次将降序排序。(最差序列)
最后得到的序列中,将小于
k
k
的值用0代替,其他用1代替,得到的01序列就是所求的,且
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个区间,我们把区间内的元素递减排列(这样就可以得到最差序列了),设为
(排序方法用 splay s p l a y 好了)
读入询问 B B 序列
枚举( k k 的范围就是数值的范围啦)
每次把和 C C 中的元素设为1, <k < k <script type="math/tex" id="MathJax-Element-80"> (其中,因为我们是顺序枚举k,每次只会改变序列中的一个值)
把 B B 中的1取负号
比较和 C C 的前缀和之和,如果所有的前缀和之和都,则 B B 序列比优秀,可以完成排序
(改变序列只以及维护前缀和之和都可以用线段树完成)
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)>>1;
if (L<=mid) change(bh<<1,l,mid,L,R,z);
if (R>mid) change(bh<<1|1,mid+1,r,L,R,z);
minn[bh]=min(minn[bh<<1],minn[bh<<1|1]);
}
以我的习惯,都是push都是在最前面的
mmp,看来要改码风了。。。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<set>
using namespace std;
const int M=1000010;
const int N=1505;
int n,m,q;
int c[N],posc[N],b[N],posb[N];
int minn[N<<2],ad[N<<2];
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-1); //大于某值的迭代器
while (l<=i&&i<r) { //[l,r)
s.erase(i);
swap(c[i],c[i+1]);
if (c[i+1]<c[i+2]&&c[i]>c[i+2]) s.insert(i+1);
if (c[i-1]<c[i]&&c[i-1]>c[i+1]) s.insert(i-1);
i=*s.upper_bound(l-1);
}
}
void push(int bh) {
if (!ad[bh]) return;
int lc=bh<<1;
int rc=bh<<1|1;
minn[lc]+=ad[bh]; ad[lc]+=ad[bh];
minn[rc]+=ad[bh]; ad[rc]+=ad[bh];
ad[bh]=0;
}
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)>>1;
if (L<=mid) change(bh<<1,l,mid,L,R,z);
if (R>mid) change(bh<<1|1,mid+1,r,L,R,z);
minn[bh]=min(minn[bh<<1],minn[bh<<1|1]);
}
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=1;i<=m;i++) scanf("%d%d",&p[i].x,&p[i].y);
for (int i=1;i<=n;i++) c[i]=i;
for (int i=1;i<n;i++) s.insert(i); //降序排列,逆序对的起点
for (int i=m;i>=1;i--) Sort(p[i].x,p[i].y);
for (int i=1;i<=n;i++) posc[c[i]]=i; //c[i]的位置
while (q--) {
for (int i=1;i<=n;i++) scanf("%d",&f[i].val),f[i].id=i;
sort(f+1,f+1+n,cmp);
for (int i=1;i<=n;i++) b[f[i].id]=i; //离散化
for (int i=1;i<=n;i++) posb[b[i]]=i; //b[i]的位置
memset(minn,0,sizeof(minn));
memset(ad,0,sizeof(ad));
bool flag=1;
for (int k=2;k<=n;k++) {
change(1,1,n,posb[k-1],n,1); //b -1 k变大,k-1的位置变成了0
change(1,1,n,posc[k-1],n,-1); //c 1 k变大,k-1的位置变成了0
if (minn[1]<0) {flag=0;break;}
}
if (flag) printf("AC\n");
else printf("WA\n");
}
return 0;
}