[題解]SP10606 Balanced Numbers

Sinktank發表於2024-04-13

SP10606 Balanced Numbers

關於最佳化方式的說明詳見數位dp例題及詳解-下

SPOJ註冊不上所以暫時無法提交w,但是3份程式碼與正解對拍沒有問題。

使用\(vis[0\sim 9]\)表示\(0\sim 9\)的訪問情況,\(sta[0\sim 9]\)表示\(0\sim 9\)填寫個數的奇偶性(奇數為\(1\),偶數為\(0\))。暴搜先打出來,然後考慮怎麼記憶化。我們發現如果兩個狀態(\(limit=false\))填寫到同一位置\(pos\),而且\(vis\)\(sta\)都相同,那麼這兩個狀態答案相同。

所以用\(f[pos][vis][sta]\)來記憶化,空間\(20*1024*1024\),不會MLE(1.46G的記憶體)

注意到資料範圍,可能需要開unsigned long long,注意這樣\(f\)陣列就不能初始化為\(-1\)了,可以再開一個bool型別的\(fv\)表示\(f\)的這個狀態是否計算出答案了。

1.1 Code
#include<bits/stdc++.h>
#define int unsigned long long
using namespace std;
int t,l,r,a[30];
bitset<10> vis,sta;
bool fv[30][1024][1024];
int f[30][1024][1024];
int dfs(int pos,bool limit,bool zero){
	if(pos==0){
		for(int i=0;i<=9;i++) if(vis[i]&&sta[i]==i%2) return 0;
		return 1;
	}
	int numvis=vis.to_ullong(),numsta=sta.to_ullong();
	if(!limit&&!zero&&fv[pos][numvis][numsta])
		return f[pos][numvis][numsta];
	int rig=limit?a[pos]:9,ans=0;
	for(int i=0;i<=rig;i++){
		bool is=(zero&&i==0);
		bool tvis=vis[i],tsta=sta[i];
		if(!is) vis[i]=1,sta[i]=!sta[i];
		ans+=dfs(pos-1,limit&&i==rig,is);
		vis[i]=tvis,sta[i]=tsta;
	}
	if(!limit&&!zero) f[pos][numvis][numsta]=ans,fv[pos][numvis][numsta]=1;
	return ans;
}
int solve(int x){
	int len=0;
	while(x){
		a[++len]=x%10;
		x/=10;
	}
	return dfs(len,1,1);
}
signed main(){
	memset(fv,0,sizeof fv);
	cin>>t;
	while(t--){
		cin>>l>>r;
		cout<<solve(r)-solve(l-1)<<endl;
	}
	return 0;
}

空間最佳化

(第3種最佳化方式)

其實上面的就能過了,但是我們注意到還有最佳化空間。

上面的表示其實就是四進位制,但是我們發現\(vis[i]=0,sta[i]=1\)的情況不存在,所以我們可以最佳化成三進位制,狀壓一下就可以了。總空間\(20*(3^{10})=20*59049\)

按道理說應該只是最佳化了空間而沒有最佳化時間,因為上面所說的情況根本不會搜尋到。

(但很奇怪的是這份程式碼跑得奇快,具體見下面的時間對比,如果大家有解答請在評論區告訴我,謝謝!)

1.2 空間最佳化Code
#include<bits/stdc++.h>
#define int unsigned long long
using namespace std;
int t,l,r,a[30];
int sta[10];
bool fv[30][59049];
int f[30][59049];
//0沒訪問,1訪問奇數次,2訪問偶數次,10位三進位制
//最佳化掉了“沒訪問過,奇數次”的狀態
int to_num(){
	int ans=0;
	for(int i=0;i<=9;i++) ans=ans*3+sta[i];
	return ans;
}
int dfs(int pos,bool limit,bool zero){
	if(pos==0){
		for(int i=0;i<=9;i++){
			if(sta[i]==0) continue;
			if(sta[i]-1!=i%2) return 0;
		}
		return 1;
	}
	int numsta=to_num();
	if(!limit&&!zero&&fv[pos][numsta])
		return f[pos][numsta];
	int rig=limit?a[pos]:9,ans=0;
	for(int i=0;i<=rig;i++){
		bool is=(zero&&i==0);
		int tsta=sta[i];
		if(!is) sta[i]=(sta[i]==0||sta[i]==2)?1:2;
		ans+=dfs(pos-1,limit&&i==rig,is);
		sta[i]=tsta;
	}
	if(!limit&&!zero) f[pos][numsta]=ans,fv[pos][numsta]=1;
	return ans;
}
int solve(int x){
	int len=0;
	while(x){
		a[++len]=x%10;
		x/=10;
	}
	return dfs(len,1,1);
}
signed main(){
	memset(fv,0,sizeof fv);
	cin>>t;
	while(t--){
		cin>>l>>r;
		cout<<solve(r)-solve(l-1)<<endl;
	}
	return 0;
}

究極時空最佳化

(第1種最佳化方式)

結論:只要vis奇數位上1的個數vis偶數位上1的個數sta奇數位上1的個數sta偶數位上1的個數都分別相等,兩種狀態答案就一樣。所以可以直接使用\(f[pos][a][b][c][d]\)來記憶化,也可以直接壓縮成\(f[pos][a]\)。空間\(20*(6^4)=20*1296\),時間也是。

為什麼呢?如果你一共訪問了\(n\)個奇數,其中有\(m(m\leq n)\)個奇數訪問了奇數次。那麼不用管具體這些奇數是幾,因為結果中奇數互相換是不會影響的。比如\(331132377\)滿足條件,那麼我把\(1\)\(7\)互換,或者把\(3\)都換成\(9\)……都不會影響結果。偶數同理。

1.3 究極時空最佳化Code
#include<bits/stdc++.h>
#define int unsigned long long
using namespace std;
int t,l,r,a[30];
bitset<10> vis,sta;
bool fv[30][1296];
int f[30][1296];
int dfs(int pos,bool limit,bool zero){
	if(pos==0){
		for(int i=0;i<=9;i++) if(vis[i]&&sta[i]==i%2) return 0;
		return 1;
	}
	int num=(vis[0]+vis[2]+vis[4]+vis[6]+vis[8]);
	num=num*6+(vis[1]+vis[3]+vis[5]+vis[7]+vis[9]);
	num=num*6+(sta[0]+sta[2]+sta[4]+sta[6]+sta[8]);
	num=num*6+(sta[1]+sta[3]+sta[5]+sta[7]+sta[9]);
	if(!limit&&!zero&&fv[pos][num])
		return f[pos][num];
	int rig=limit?a[pos]:9,ans=0;
	for(int i=0;i<=rig;i++){
		bool is=(zero&&i==0);
		bool tvis=vis[i],tsta=sta[i];
		if(!is) vis[i]=1,sta[i]=!sta[i];
		ans+=dfs(pos-1,limit&&i==rig,is);
		vis[i]=tvis,sta[i]=tsta;
	}
	if(!limit&&!zero){
		f[pos][num]=ans,fv[pos][num]=1;
	}
	return ans;
}
int solve(int x){
	int len=0;
	while(x){
		a[++len]=x%10;
		x/=10;
	}
	return dfs(len,1,1);
}
signed main(){
	memset(fv,0,sizeof fv);
	cin>>t;
	while(t--){
		cin>>l>>r;
		cout<<solve(r)-solve(l-1)<<endl;
	}
	return 0;
}

執行消耗對比

[題解]SP10606 Balanced Numbers

從上到下分別是洛谷題解排名第一、樸素演算法、空間最佳化、究極時空最佳化的程式碼的執行消耗,每個樣例測試點均在\(5\)個以內。所以可以看出,樸素演算法即可透過此題,而最佳化後的程式碼,無論在時間還是空間方面,均比題解優。

相關文章