[IOI2021]位移寄存器

14 篇文章 0 订阅

题目

传送门 to LOJ

思路

这篇博客讲得很好。我只做一些简要说明。

子任务一:求 min ⁡ \min min 值。这个其实就是卡常的常识,位运算消除分支。而且,如果你看过 O 2 \rm O2 O2 编译下的汇编代码,你会发现 O 2 \rm O2 O2 也是这样干的。

只需要取出 ( a − b ) (a-b) (ab) 的符号位,用它填满 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) (ab) 呢?先将 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 呢……

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值