1. 程式人生 > 實用技巧 >【正睿2019暑假集訓】正睿889 小D與計算

【正睿2019暑假集訓】正睿889 小D與計算

task1

相當於要把第一位取反。可以先把暫存器\(1\)取反,放到暫存器\(2\)。然後對暫存器\(2\),先左移\(63\)位,再右移\(63\)位。利用自然溢位,就相當於只保留了最低位。

需要\(3\)次操作。

task2

考慮三個暫存器\(x\), \(y\), \(z\)。當我們說,對\(x\), \(y\), \(z\)進行“計算”時,表示的其實是對這三個暫存器裡對應的值操作。

我們要求出\(x+y\)(也就是編號為\(x\)的暫存器和編號為\(y\)的暫存器裡對應的值相加)。

先考慮不進位的加法,也就是異或。先令\(z:=x\operatorname{xor}y\)。然後再考慮進位的部分,它等於\((x\operatorname{and}y)\ll 1\)

。令\(y:=(x\operatorname{and}y)\ll 1\)。我們驚喜地發現,問題就轉化為了\(y\)\(z\)的加法!並且,每次\(y\)一定會左移\(1\)位,那麼至多\(64\)次之後,\(y\)就會變成\(0\),此時加法就完成了!

每輪操作,根據當前輪數的奇偶性不同,考慮是用\(x+y\)還是\(z+y\)。相當於每隔一輪,\(x\), \(z\)這兩個編號的含義會交換一下。這樣的好處是避免了每一輪結束時我強行set x z,這樣就多一次操作了。因為總輪數\(64\)是一個偶數,你會發現,最終答案一定儲存在\(z\)的位置。

因為每輪需要用到xor, and, shl共三次操作,所以總共用到\(3\times64=192\)

次操作。實際可能更多或更少(例如最後一次左移可以不做)。

task3

依次考慮每一位(每次左移\(1\)位,再\(\operatorname{and} 1\)。這個\(1\)可以提前預處理好,方法是not t t, shr t 63)。對當前位,得到的是一個\(0\)\(1\)的數字。呼叫task2裡的加法,讓答案加上這個數字即可。每次加法不需要加\(64\)位,因為答案數字很小,只需要加到當前可能的最高位即可。

大約需要\(1100\)次操作。

task4

考慮\(x\operatorname{xor}y\)的最高位,也就是\(x\), \(y\)第一個不同的位。如果我們能只保留這一位上的數字,不妨記為\(z\)

。那麼用\(z\operatorname{and}x\),如果結果不為\(0\),則\(x>y\),否則\(x\leq y\)

於是問題轉化為如何只保留一個數的最高位

考慮如下的操作:

x|=(x>>1);
x|=(x>>2);
x|=(x>>4);
x|=(x>>8);
x|=(x>>16);
x|=(x>>32);

這樣\(6\)次操作後,實現的效果是:\(x\)的最高位以下,全都是\(1\)。原理其實就是倍增法:先把最高位的下一位變成\(1\),再用這兩位一起去把下兩位變成\(1\),以此類推。另外,雖然看上去是\(6\)步操作,但實際實現時,每步操作需要\(3\)條指令,所以這個過程共需要\(18\)條指令。

用這個方法,我們可以先把\(z\)的最高位以下全都變成\(1\)。然後,我們用三次操作,令\(z:=z\operatorname{xor}(z\gg1)\)。這樣就相當於只保留了\(z\)的最高位。拿新的\(z\)去和\(x\) \(\operatorname{and}\)

最後判斷結果是否為\(0\),可以再用一次倍增法,把最高位的\(1\)(如果有的話)推向最低位,然後左移\(63\)位再右移\(63\)位,就能只保留最低位的值。

共需要\(43\)次操作。

task5

我們還是依次考慮每一位。維護一個當前答案。如果當前位是\(1\),就令:\(\text{ans}:=\text{ans}\times2+1\)

這相當於要實現一個三目運算子:如果條件為真,就令\(\text{ans}\)等於\(x\),否則令\(\text{ans}\)等於\(y\)(這裡\(x\), \(y\),是一般性的表述。在這裡就等於\(\text{ans}\times2+1\)\(\text{ans}\))。然而我們沒有\(\texttt{if}\)語句,如何實現三目運算子呢?

考慮如果條件為真,就構造一個全\(1\)的數(也就是\(2^{64}-1\)),這可以用task4裡的倍增法來實現;條件不為真時,這個數自然為全\(0\)。然後用構造出的這個(全\(1\)或全\(0\)的)數,去\(\operatorname{and} x\)。再將其取反,去\(\operatorname{and}y\)。發現這兩個結果,必有一個是\(0\),另一個是我們想要的值,所以將它們\(\operatorname{or}\)起來,賦給\(\text{ans}\)即可。

本task裡,這個“條件為真”,就相當於當前位上的數是否是\(1\)。所以具體來說就是把當前位上的數,用倍增法鋪滿所有位即可。

這只是一個大致的思路,樸素實現的話操作次數比較多(\(1600\sim 1800\)左右),需要做一些優化,例如:

  • 從小到大考慮所有二進位制位,對於第\(i\)位,\(\text{ans}\)的位數一定小於等於\(i\)。所以不需要鋪滿所有\(64\)個二進位制位,只需要把前\(i\)位鋪滿即可。
  • 發現三目運算要選擇的兩個數\(x\), \(y\),只有一個二進位制位不同。所以不需要把條件取反再\(\operatorname{and}\)。直接先\(\operatorname{and}\)一遍較大的那個數(\(\text{ans}\times2+1\)),然後把兩者\(\operatorname{or}\)起來即可。

task6

可以選擇“氣泡排序”或者“選擇排序”。核心是要實現:if(x>y) swap(x,y);

可以用task4實現比較。再用倍增法把比較的結果鋪滿所有位,記這個結果為\(t\)\(t\in\{0,2^{64-1}\}\))。

有了比較結果之後,剩下的又相當於一個三目運算子。不過樸素實現還是運算元量太大(\(7\)次)。考慮“交換”操作的特性。可以用\(\operatorname{xor}\)來實現。先搞一個\(z=x\operatorname{xor}y\)。那麼如果需要交換,就相當於讓兩個數都異或上\(z\)。所以可以令\(z:=z\operatorname{and}t\)。然後再把\(x\), \(y\)分別\(\operatorname{xor}z\)即可。共需要\(4\)次操作。

我實現下來,總共是\(2268\)次操作。常數大一些應該也不會超過\(2400\)

製造答案的程式碼

//problem:ZR889
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

template<typename T>inline void ckmax(T& x,T y){x=(y>x?y:x);}
template<typename T>inline void ckmin(T& x,T y){x=(y<x?y:x);}

int get_highbit(ull x){
	int ans=0;
	while(x)ans++,x>>=1;
	return ans;
}

int add(int xpos=1,int ypos=2,int anspos=3,int highbit=64) {
	//不需要a[anspos]=0
	//不還原a[xpos],a[ypos]
	int x=xpos;
	int y=ypos;
	int z=anspos;
	
	int cnt=0;
	for(int i=1;i<=highbit;++i){
		cout<<"xor "<<z<<" "<<x<<" "<<y<<endl;++cnt;
		cout<<"and "<<y<<" "<<x<<" "<<y<<endl;++cnt;
		swap(x,z);
		if(i<highbit){
			cout<<"shl "<<y<<" "<<1<<endl;++cnt;
		}
	}
	if(x!=anspos){
		cout<<"set "<<anspos<<" "<<x<<endl;++cnt;
	}
//	if(highbit!=64){
//		cout<<"shl "<<anspos<<" "<<64-highbit<<endl;++cnt;
//		cout<<"shr "<<anspos<<" "<<64-highbit<<endl;++cnt;
//	}
	return cnt;
}
int popcnt(int pos=1,int anspos=2){
	//預設a[anspos]=0
	//不還原a[pos]
	int cnt=0;
	int t1=40;
	cout<<"not "<<t1<<" "<<t1<<endl;++cnt;
	cout<<"shr "<<t1<<" "<<63<<endl;++cnt;
	int v=39;
	int s1=38;
	int s2=anspos;
	for(int i=1;i<=64;++i){
		cout<<"and "<<v<<" "<<pos<<" "<<t1<<endl;++cnt;
		//cout<<"set "<<s1<<" "<<anspos<<endl;++cnt;
		cnt+=add(s1,v,s2,get_highbit(i));
		swap(s1,s2);
		if(i<64){
			cout<<"shr "<<pos<<" "<<1<<endl;++cnt;
		}
	}
	if(s1!=anspos){
		cout<<"set "<<anspos<<" "<<s1<<endl;++cnt;
	}
	return cnt;
}
int push_highbit(int pos,int highbit=64){
	//anspos=pos
	//把最高位以下全部搞成1
	int cnt=0;
	int t=20;
	for(int i=1;i<highbit;i<<=1){
		cout<<"set "<<t<<" "<<pos<<endl;++cnt;
		cout<<"shr "<<t<<" "<<i<<endl;++cnt;
		cout<<"or "<<pos<<" "<<pos<<" "<<t<<endl;++cnt;
	}
	return cnt;
}
int compare(int xpos=1,int ypos=2,int anspos=3){
	//不需要a[anspos]=0
	//不會改變a[xpos],a[ypos]
	int cnt=0;
	int t1=40;
	int t2=39;
	cout<<"xor "<<t1<<" "<<xpos<<" "<<ypos<<endl;++cnt;
	cnt+=push_highbit(t1);
	
	cout<<"set "<<t2<<" "<<t1<<endl;++cnt;
	cout<<"shr "<<t2<<" "<<1<<endl;++cnt;
	cout<<"xor "<<t1<<" "<<t1<<" "<<t2<<endl;++cnt;//現在t1只有最高位是1了
	
	cout<<"and "<<anspos<<" "<<t1<<" "<<xpos<<endl;++cnt;
	cnt+=push_highbit(anspos);
	//task6裡可以註釋掉:
	cout<<"shl "<<anspos<<" "<<63<<endl;++cnt;
	cout<<"shr "<<anspos<<" "<<63<<endl;++cnt;
	return cnt;
}
int pow_of_popcount(int pos=1,int anspos=2){
	//預設a[anspos]=0
	int cnt=0;
	int flagpos=40;
	//cout<<"shl "<<flagpos<<" "<<64<<endl;++cnt;
	cout<<"not "<<flagpos<<" "<<flagpos<<endl;++cnt;
	cout<<"shr "<<flagpos<<" "<<63<<endl;++cnt;
	int t1=39;
	int t2=38;
	int t3=37;
	cout<<"set "<<t1<<" "<<flagpos<<endl;++cnt;
	for(int i=1;i<=64;++i){
		if(i!=1){
			cout<<"set "<<t2<<" "<<anspos<<endl;++cnt;
			cout<<"shl "<<t2<<" "<<1<<endl;++cnt;
		}
		cout<<"or "<<t2<<" "<<t2<<" "<<t1<<endl;++cnt;//t2=anspos<<1|1
		
		cout<<"and "<<t3<<" "<<pos<<" "<<flagpos<<endl;++cnt;
		cnt+=push_highbit(t3,i);
		cout<<"and "<<t2<<" "<<t2<<" "<<t3<<endl;++cnt;
		
		cout<<"or "<<anspos<<" "<<anspos<<" "<<t2<<endl;++cnt;
		
		if(i<64){
			cout<<"shl "<<flagpos<<" "<<1<<endl;++cnt;
		}
	}
	return cnt;
}
int push_lowbit(int pos){
	int cnt=0;
	int t=20;
	for(int i=1;i<=32;i<<=1){
		cout<<"set "<<t<<" "<<pos<<endl;++cnt;
		cout<<"shl "<<t<<" "<<i<<endl;++cnt;
		cout<<"or "<<pos<<" "<<pos<<" "<<t<<endl;++cnt;
	}
	return cnt;
}
int bubble_sort(){
	int cnt=0;
	int t1=10;
	int t2=11;
	for(int i=1;i<9;++i){
		for(int j=i+1;j<=9;++j){
			cnt+=compare(i,j,t1);
			cnt+=push_lowbit(t1);
			cout<<"xor "<<t2<<" "<<j<<" "<<i<<endl;++cnt;
			cout<<"and "<<t2<<" "<<t2<<" "<<t1<<endl;++cnt;
			cout<<"xor "<<i<<" "<<i<<" "<<t2<<endl;++cnt;
			cout<<"xor "<<j<<" "<<j<<" "<<t2<<endl;++cnt;
			
//			cout<<"and "<<t2<<" "<<t1<<" "<<j<<endl;++cnt;
//			cout<<"and "<<t3<<" "<<t1<<" "<<i<<endl;++cnt;
//			cout<<"not "<<t1<<" "<<t1<<endl;++cnt;
//			cout<<"and "<<t4<<" "<<t1<<" "<<j<<endl;++cnt;
//			cout<<"and "<<t5<<" "<<t1<<" "<<i<<endl;++cnt;
//			
//			cout<<"or "<<j<<" "<<t3<<" "<<t4<<endl;++cnt;
//			cout<<"or "<<i<<" "<<t2<<" "<<t5<<endl;++cnt;
		}
	}
	return cnt;
}

void task1(){
	freopen("calculate1.ans","w",stdout);
	cout<<"not "<<2<<" "<<1<<endl;
	cout<<"shl "<<2<<" "<<63<<endl;
	cout<<"shr "<<2<<" "<<63<<endl;
	cerr<<"cnt "<<3<<endl;
}
void task2(){
	freopen("calculate2.ans","w",stdout);
	int cnt=add();
	cerr<<"cnt "<<cnt<<endl;
}
void task3(){
	freopen("calculate3.ans","w",stdout);
	int cnt=popcnt();
	cerr<<"cnt "<<cnt<<endl;
}
void task4(){
	freopen("calculate4.ans","w",stdout);
	int cnt=compare();
	cerr<<"cnt "<<cnt<<endl;
}
void task5(){
	freopen("calculate5.ans","w",stdout);
	int cnt=pow_of_popcount();
	cerr<<"cnt "<<cnt<<endl;
}
void task6(){
	freopen("calculate6.ans","w",stdout);
	int cnt=bubble_sort();
	cerr<<"cnt "<<cnt<<endl;
}
int main() {
	//...
	return 0;
}