1. 程式人生 > 實用技巧 >洛谷 P6218 [USACO06NOV] Round Numbers S

洛谷 P6218 [USACO06NOV] Round Numbers S

洛谷 P6218 [USACO06NOV] Round Numbers S

題目描述

如果一個正整數的二進位制表示中,\(0\) 的數目不小於 \(1\) 的數目,那麼它就被稱為「圓數」。

例如,\(9\) 的二進位制表示為 \(10011001\),其中有 \(2\)\(0\)\(2\)\(1\)。因此,\(9\) 是一個「圓數」。

請你計算,區間 \([l,r]\) 中有多少個「圓數」。

輸入格式

一行,兩個整數 \(l,r\)

輸出格式

一行,一個整數,表示區間 \([l,r]\)中「圓數」的個數。
輸入輸出樣例

輸入 #1

2 12

輸出 #1

6

說明/提示

【資料範圍】

對於 \(100\%\) 的資料,\(1\le l,r\le 2\times 10^9\)

【樣例說明】

區間 \([2,12]\) 中共有 \(6\) 個「圓數」,分別為 \(2,4,8,9,10,12\)

分析

比較套路的數位 \(DP\)

數位 \(DP\) 的實質就是換一種暴力列舉的方式,使得新的列舉方式滿足 \(DP\) 的性質,然後記憶化就可以了。

首先,我們要進行 \(DP\) 的話,肯定要定義一個 \(f\) 陣列儲存我們計算過的值

因為這道題和數位有關,所以第一位我們要定義當前遍歷到了第幾位

而且我們還要判斷二進位制下 \(0\) 的數量和 \(1\) 的數量

所以,我們設 \(f[i][j][k]\)

為當前遍歷到第 \(i\) 位,二進位制下 \(1\) 的數量為 \(j\)\(0\) 的數量為 \(j\) 的數的個數

主函式我們用差分的思想搞一下即可

signed main(){
	memset(f,-1,sizeof(f));
	int l,r;
	scanf("%lld%lld",&l,&r);
	printf("%lld\n",solve(r)-solve(l-1));
	return 0;
}

然後是 \(solve\) 函式

這裡的 \(cnt\) 是用來記錄當前的數在二進位制下有多少位,\(num\) 陣列是用來記錄這個數每一二進位制位上的數字的

這個函式的變數只有一個 \(xx\), 返回值是 \(0\)\(xx\) 之間圓數的個數

int solve(int xx){
	memset(num,0,sizeof(num));
	cnt=0;
	while(xx){
		num[++cnt]=xx&1ll;
		xx>>=1ll;
	}
	return dfs(cnt,0,0,1,1);
}

下面的 \(dfs\) 函式是最重要的部分

int dfs(int ws,int tot1,int tot0,bool lim,bool zer){
	if(ws==0) {
		if(tot1<=tot0) return 1;
		return 0;
	}
	if(lim==0 && zer==0 && f[ws][tot1][tot0]!=-1) return f[ws][tot1][tot0];
	int up=1,ans=0;
	if(lim) up=num[ws];
	for(int i=0;i<=up;i++){
		if(zer==1 && i==0) ans+=dfs(ws-1,0,0,lim && i==up,1);
		else ans+=dfs(ws-1,tot1+(i==1),tot0+(i==0),lim && i==up,0);
	}
	if(lim==0 && zer==0)f[ws][tot1][tot0]=ans;
	return ans;
}

它的五個引數分別為:當前處理到第 \(ws\)

\(0\) 的個數 \(tot0\) ,\(1\) 的個數 \(tot1\)

\(lim\) 特判前一位是否為範圍內的最大值

\(zer\) 記錄有沒有前導零

終止條件就是處理到最後一位

具體的邊界看一下下面的模板

程式碼

#include<bits/stdc++.h>
using namespace std;
#define int long long
int f[60][60][60],num[55],cnt,sum[55];
const int mod=1e7+7;
int dfs(int ws,int tot1,int tot0,bool lim,bool zer){
	if(ws==0) {
		if(tot1<=tot0) return 1;
		return 0;
	}
	if(lim==0 && zer==0 && f[ws][tot1][tot0]!=-1) return f[ws][tot1][tot0];
	int up=1,ans=0;
	if(lim) up=num[ws];
	for(int i=0;i<=up;i++){
		if(zer==1 && i==0) ans+=dfs(ws-1,0,0,lim && i==up,1);
		else ans+=dfs(ws-1,tot1+(i==1),tot0+(i==0),lim && i==up,0);
	}
	if(lim==0 && zer==0)f[ws][tot1][tot0]=ans;
	return ans;
}
int solve(int xx){
	memset(num,0,sizeof(num));
	cnt=0;
	while(xx){
		num[++cnt]=xx&1ll;
		xx>>=1ll;
	}
	return dfs(cnt,0,0,1,1);
}
signed main(){
	memset(f,-1,sizeof(f));
	int l,r;
	scanf("%lld%lld",&l,&r);
	printf("%lld\n",solve(r)-solve(l-1));
	return 0;
}