1. 程式人生 > 其它 >2020 ICPC 亞洲區域賽(上海)C Sum of Log (數位dp)

2020 ICPC 亞洲區域賽(上海)C Sum of Log (數位dp)

技術標籤:=====DP=====

題鏈:https://ac.nowcoder.com/acm/contest/9925/C

題意:求 \\ \left \lfloor log2(i+j) + 1 \right \rfloor \quad when \quad i \& j==0 \quad \sum_{i=0}^{x} \quad \sum_{j=[i==0]}^{y}

思路:首先,暴力的話就是列舉i,j。那麼演算法的話就很容易想到數位dp。看到&運算,肯定是二進位制,那就是數二進位制位。

再看\\ \left \lfloor log2(i+j) + 1 \right \rfloor,因為要求i&j==0,那麼i和j中每一位中都最多隻能有一個1(每一位只能有00,01,10,3種情況);

又因為log運算,那麼i,j中最高位的1是第幾位就是\\ \left \lfloor log2(i+j) + 1 \right \rfloor的值。

那麼,我們列舉每一個最高位(也就是列舉\\ \left \lfloor log2(i+j) + 1 \right \rfloor的值),然後數位dp算i&j==0的個數就行了。

(PS:知道是數位dp,奈何不太熟練,不會定狀態,一直T,還是菜。)

程式碼1:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod = 1e9+7;
const int N = 50;
ll dp[N][2][2];
//dp[len][j][k]:從第len位開始數,i是否上限,j是否上限時i&j==0的個數 
int digita[N],digitb[N];
ll a,b;
ll Mo(ll x){
	if(x>=mod) x-=mod;
	return x;
}
ll dfs(int len,bool limita,bool limitb){
	if(len==0) return 1;
	if(dp[len][limita][limitb]!=-1) return dp[len][limita][limitb];
	int upa=limita?digita[len]:1;
	int upb=limitb?digitb[len]:1;
	ll res=0;
	for(int i=0;i<=upa;i++){
		for(int j=0;j<=upb;j++){
			int x=i&j;
			if(x==1) continue;
			res=(res+dfs(len-1,limita&&i==upa,limitb&&j==upb))%mod;
			//res=Mo(res);			
		}
	}
	return dp[len][limita][limitb]=res;
}
ll cal(ll a,ll b){
	memset(digita,0,sizeof digita);
	memset(digitb,0,sizeof digitb);
	int lena=0,lenb=0;
	while(a) digita[++lena]=(a&1),a>>=1;
	while(b) digitb[++lenb]=(b&1),b>>=1;
	ll res=0;
	memset(dp,-1,sizeof dp);
	//i的最高位確定Log2(i+j)+1的值 
	for(int i=lena;i>=1;i--){
		//i的第i位為1,j的第i位為0
		//i-1:數第i-1位
		//i==lena:當i==lena時才是上限,否則不是上限
		//i>lenb:當i>lenb時才是上限 
		res+=(dfs(i-1,i==lena,i>lenb)*i)%mod;
		res%=mod;
	} 
	memset(dp,-1,sizeof dp);
	//j的最高位確定Log2(i+j)+1的值 
	for(int i=lenb;i>=1;i--){
		//i的第i位為0,j的第i位為1
		//i-1:數第i-1位
		//i>lena:當i>lena時才是上限,否則不是上限
		//i==lenb:當i==lenb時才是上限 
		res+=(dfs(i-1,i>lena,i==lenb)*i)%mod;
		res%=mod;
	} 
	return res;
}
int main(void){
//	freopen("input.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int t;
	scanf("%d",&t);
	while(t--){
		scanf("%lld%lld",&a,&b);
		printf("%lld\n",cal(a,b));
	}	

	return 0;	
} 

程式碼2(超時): T太多了,足足1e5,要是1e4或者1e3應該就過了。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int mod = 1e9+7;
const int N = 50;
int dp[N][N][2][2];
int digita[N],digitb[N];
int a,b;
inline int Read()     
{
    int res=0,ch,flag=0;
    if((ch=getchar())=='-')
        flag=1;
    else if(ch>='0'&&ch<='9')
        res=ch-'0';
    while((ch=getchar())>='0'&&ch<='9')
        res=res*10+ch-'0';
    return flag?-res:res;
}
inline void Write(int a)    
{
    if(a>9)
        Write(a/10);
    putchar(a%10+'0');
}
int Mo(int x){
	if(x>=mod) x-=mod;
	return x;
}
//len:數到第幾位
//pos: 最高位是第幾位
//limita: i是否為上限
//limitb: j是否為上限 
int dfs(int len,int pos,bool limita,bool limitb){
	if(len==0) return (pos==33)?1:pos;
	if(dp[len][pos][limita][limitb]!=-1) return dp[len][pos][limita][limitb];
	int upa=limita?digita[len]:1;
	int upb=limitb?digitb[len]:1;
	int res=0;
	for(int i=0;i<=upa;i++){
		for(int j=0;j<=upb;j++){
			if(i==1&&j==1) continue;
			int np;
			if(pos!=33) np=pos;
			else if(i==0&&j==0) np=pos;
			else np=len;
			res=(res+dfs(len-1,np,limita&&i==upa,limitb&&j==upb)),res=Mo(res);			
		}
	}
	return dp[len][pos][limita][limitb]=res;
}
int cal(int a,int b){
	int lena=0,lenb=0;
	while(a) digita[++lena]=(a&1),a>>=1;
	while(b) digitb[++lenb]=(b&1),b>>=1;
	int len=max(lena,lenb);
	int i=lena+1;
	while(i<=len) digita[i++]=0;
	i=lenb+1;
	while(i<=len) digitb[i++]=0;
	for(int i=len;i>=1;i--){
		dp[i][33][0][0]=dp[i][33][0][1]=dp[i][33][1][0]=dp[i][33][1][1]=-1;
		for(int j=len;j>=1;j--){
			for(int k=0;k<=1;k++){
				for(int x=0;x<=1;x++){
					dp[i][j][k][x]=-1;
				}
			}
		}
	}
	return dfs(len,33,1,1);
}
int main(void){
//	freopen("input.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int t;
	t=Read();
	while(t--){
		a=Read(),b=Read();;
		printf("%d\n",Mo(cal(a,b)-1+mod));
	}	

	return 0;	
}