1. 程式人生 > 實用技巧 >[故地重遊][NOIP2019]格雷碼

[故地重遊][NOIP2019]格雷碼

題目

洛谷5657

解說

去年NOIP省賽題,當時就學了1個月OI所以第一題就慘遭爆零,如今再回來刷一遍過了……

總體思路就是把格雷碼當成十進位制的數字進行處理,最後再轉化回二進位制。轉化後的樣子如下:

\(0 \ 1\)

\(0 \ 1 \ 3 \ 2\)

\(0 \ 1 \ 3 \ 2 \ 6 \ 7 \ 5 \ 4\)

\(\dots\)

假設最終結果為\(f(n,k)\)

不難發現如果要求的數在該行的左半邊:

\(k<2^{n-1}\)

那麼\(f(n,k)=f(n-1,k)\)

如果要求的數在該行的右半邊:

\(k>=2^{n-1}\)

那麼\(f(n,k)=2^{n-1}+f(n,2^{n}-1-k)\)

這樣的話我們就可以用遞迴來解決,最後的返回條件就是\(n=1\)時若\(k=0\)則返回\(0\),若\(k=1\)則返回\(1\)

(以下程式碼中\(ll\)代表\(unsigned \ long \ long\)

ll dfs(int num,ll a){
	if(num==1&&a==0) return 0;
	if(num==1&&a==1) return 1;
	if(a<=power(num-1)-1) return dfs(num-1,a);
	return power(num-1)+dfs(num,power(num)-1-a);
}

但這時我們注意到一個問題:\(num=64\)

\(2^{64}\)會炸\(unsigned \ long \ long\)……

那我們特判一下不就行了。

ll dfs(int num,ll a){
	if(num==1&&a==0) return 0;
	if(num==1&&a==1) return 1;
	if(a<=power(num-1)-1) return dfs(num-1,a);
	if(num<64) return power(num-1)+dfs(num,power(num)-1-a);
	else return power(num-1)+dfs(num,power(63)-1+power(63)-a);
}

最後再把數字轉化為二進位制即可。

程式碼

#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;//記得開unsigned long long
int n;
ll k;
bool ans[70];
ll power(int x){//快速冪
	ll ans=1,a=2;
	while(x){
		if(x&1) ans*=a;
		x>>=1;
		a*=a;
	}
	return ans;
}
ll dfs(int num,ll a){//遞迴
	if(num==1&&a==0) return 0;
	if(num==1&&a==1) return 1;
	if(a<=power(num-1)-1) return dfs(num-1,a);
	if(num<64) return power(num-1)+dfs(num,power(num)-1-a);
	else return power(num-1)+dfs(num,power(63)+9223372036854775807-a);
}
void change(ll x){//轉化為二進位制
	int cnt=0;
	while(x){
		ans[cnt]=x%2;
		x/=2;
		cnt++;
	}
}
int main(){
	cin>>n>>k;
	change(dfs(n,k));
	for(int i=n-1;i>=0;i--) cout<<ans[i];
	return 0;
}

幸甚至哉,歌以詠志。