题目
思路
这篇博客讲得很好。我只做一些简要说明。
子任务一:求 min \min min 值。这个其实就是卡常的常识,位运算消除分支。而且,如果你看过 O 2 \rm O2 O2 编译下的汇编代码,你会发现 O 2 \rm O2 O2 也是这样干的。
只需要取出 ( a − b ) (a-b) (a−b) 的符号位,用它填满 k k k 个比特。用代码实现,填满方式是 ( ∼ s ) + 1 (\sim s)+1 (∼s)+1,即 s s s 按位取反后再加 1 1 1 。它实际上就是取相反数。而 O 2 \rm O2 O2 的实现就比较粗暴了,因为汇编右移指令会自动用符号位填充高位……
填满之后,分别与 a , b a,b a,b 取按位与,二者求异或和。因为二者之一会是零,另一个会不变。
而操作次数只有 O ( log n ) \mathcal O(\log n) O(logn),我们需要考虑 并行。因为对寄存器右移,实际上等价于对多个数同时右移。简单的想法是后一半跟前一半比较,但是这样会导致 “符号位” 没有空间;所以我们用奇数位和偶数位比较。
怎样得到 ( a − b ) (a-b) (a−b) 呢?先将 b b b 按位取反,再做加法。同时,我们会很高兴地发现,此时 “符号位” 已经是 k k k 个 0 0 0 或 1 1 1 了,类似于汇编的右移指令!直接右移即可。
子任务二:排序。这个就比较复杂了,一方面我们没有条件语句,另一方面我们要保持 O ( n ) \mathcal O(n) O(n) 的操作数。
没有条件语句,归并排序和快速排序就嗝屁了。冒泡是可行的,需要并行,并行的结果就是 奇偶移项排序:考虑奇数位与后面的数交换,考虑偶数位与后面的数交换,重复该过程。这个 i d e a \rm idea idea 在某场 CF \textrm{CF} CF 考过,当时需要算,多少轮(上面描述了两轮的情况)能够完成排序。答案不超过 n n n 轮,证明从略。
上面已经可以通过了,但我们还有一个更强的做法是,奇偶归并排序。这是 in-place merge sort \text{in-place merge sort} in-place merge sort 的一种,正常时间复杂度是 O ( n log 2 n ) \mathcal O(n\log^2 n) O(nlog2n) 。归并还是要归并,即两边先递归。重点在于, in-place \text{in-place} in-place 合并两个有序数列。不妨设 n n n 为 2 2 2 的幂。
首先,将奇数位数字取出,然后 in-place \text{in-place} in-place 合并。同理,将偶数位也 in-place \text{in-place} in-place 合并。而后,只需要考虑每个偶数位的数字与后一个数字交换即可。
证明:在合并序列前,对于每个偶数位的数字,它前面的数字都小于自己(因为两个子数列都有序)。所以,递归合并后,对于第 k k k 大的偶数位的数字,至少有 k k k 个奇数位的数字小于自己,即第 k k k 大的奇数位数字小于该数。另一方面,除了两个子数列的首元素,奇数位数字前也有小于自己的偶数位元素。所以第 k k k 大的偶数位数字小于 ( k + 2 ) (k{\rm+}2) (k+2) 大的奇数位数字。
于是,第 k k k 大的偶数位数字,只需要跟第 ( k + 1 ) (k{\rm+}1) (k+1) 大的奇数位数字比较。这个数字就是它后面那个数。证毕。复杂度 T m ( n ) = 2 T m ( n 2 ) + O ( n ) = O ( n log n ) T_m(n)=2T_m({n\over 2})+\mathcal O(n)=\mathcal O(n\log n) Tm(n)=2Tm(2n)+O(n)=O(nlogn) 。
合并是 O ( n log n ) \mathcal O(n\log n) O(nlogn) 的,总复杂度就是 O ( n log 2 n ) \mathcal O(n\log^2n) O(nlog2n) 。但是在本题中,并行的能力是很强的,只要比较的元素之间等距,就可以并行。对奇数位合并,和对偶数位合并,二者可以直接并行,所以合并操作次数是 T m ( n ) = T m ( n 2 ) + O ( 1 ) = O ( log n ) T_m(n)=T_m({n\over 2})+\mathcal O(1)=\mathcal O(\log n) Tm(n)=Tm(2n)+O(1)=O(logn),同理递归左右子数列进行排序也可并行,复杂度 T ( n ) = T ( n 2 ) + T m ( n ) = O ( log 2 n ) T(n)=T({n\over 2})+T_m(n)=\mathcal O(\log^2 n) T(n)=T(2n)+Tm(n)=O(log2n),实乃壮举!
代码
本题的限制较为宽松,选择容易实现的方法即可。
#include "registers.h"
#include <vector>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
const int m = 100, b = 2000;
vector<bool> mask;
void memset(vector<bool>::iterator it,bool val,int len){
for(; len; --len,++it) *it = val; // similar to pointer
}
void construct_instructions(int s, int n, int k, int q){
mask.resize(b); // and never change
memset(mask.begin(),false,b); // clear
if(s == 0){ // I hate melded 2 questions
if(n&(n-1)){ // not power of 2
memset(mask.begin()+n*k,true,(128-n)*k), n = 128;
append_store(1,mask), append_or(0,0,1);
}
for(int len=1; len!=n; len<<=1){
memset(mask.begin(),false,n*k);
for(int i=0; i!=n; i+=(len<<1))
memset(mask.begin()+(i+len)*k,true,k);
append_store(1,mask), append_and(2,0,1);
append_xor(0,0,2); // remove odd numbers
append_right(2,2,len*k); // align odd and even numbers
append_not(3,2); // negate odd numbers
append_add(4,0,3); // add to get sign
append_right(4,4,k); // fill number with sign
append_and(0,0,4); // if sign = 1, then a is smaller
append_not(4,4); append_and(2,2,4); // else b <= a
append_xor(0,0,2); // one of them is empty
}
}
if(s == 1){ // difficult sort
if(n&1){ // assume even elements
memset(mask.begin()+n*k,true,k);
append_store(1,mask), append_or(0,0,1);
memset(mask.begin()+n*k,false,k), ++ n;
}
memset(mask.begin(),true,k);
append_store(0xF,mask); // cover first element
memset(mask.begin(),false,k);
memset(mask.begin()+(n-1)*k,true,k);
append_store(0xE,mask); // cover last element
rep(par,0,1){
memset(mask.begin(),false,n*k);
for(int i=(par^1)<<1; i!=n; i+=2)
memset(mask.begin()+(i^par)*k,true,k);
append_store(5+par,mask); // save mask
}
rep(cs,1,n) rep(par,0,1){
append_and(2,0,5+par); // pre-computed
append_xor(0,0,2), append_right(2,2,k);
if(!par) append_or(2,2,0xE); // INFTY
append_not(3,2), append_add(4,0,3), append_right(4,4,k);
if(!par) append_or(4,4,0xF); // cover first element
append_and(0xA,0,4); // if sign = 1, then a is smaller
append_not(4,4); append_and(0xB,2,4); // else b <= a
append_xor(0xC,0,2); // original xor value
append_xor(0,0xA,0xB); // one of them is empty
append_xor(0xC,0xC,0); // get another one
append_left(0xC,0xC,k), append_xor(0,0,0xC);
}
}
}
后记
谁告诉你 bool[] v
表示 vector<bool> v
的?我本以为这只是误用了
java-style
\text{java-style}
java-style 呢……