洛谷P6218

感觉此题是P4317 花神的数论题的变形版


Description

求一段区间内二进制中 (0) 的个数不小于 (1) 的个数的数的个数


Solution

数位 DP

先考虑状态转移方程式,如何处理处所有数

(f[i][j][k]) 表示枚举到第 (i) 位,数字为 (j),此时二进制中 (1) 的个数为 (k)

显然有

[f[i][0][j]=sum_{k=0}^if[i-1][1][k]+f[i-1][0][k] ]

[f[i][1][j]=sum_{k=1}^if[i-1][0][k-1]+f[i-1][1][k-1] ]

与花神的数论题是一样的

另一部分,考虑答案的统计

显然,对于 (ans_{l,r}),可以转化为 (ans_{1,r}-ans_{1,l-1})

把最高位上的数与最高位以下的分开统计

由于存在原数定义的限制,我们对于 (ans_{1,V}) 可用如下方法处理

(len)(V) 的位数, (a_i)(V) 的第 (i)

对于首位为 (1) 且前面有 (cnt1)(1)(f)

(res+=f[i][0][j],iin[1,len),jin[0,len/2-cnt1])

对于首位为 0 的 f,

(res+=f[i][1][j],iin[1,len),jin[0,i/2])


Code

#include<cstdio>
#include<iostream>
#include<cstring> 
#include<cmath>
#include<algorithm>
#define maxn 10000100	
//#define int long long

using namespace std;

int f[50][2][50],a[50];
int l,r;

int read(){
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-') w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9') s=(s<<1)+(s<<3)+ch-'0',ch=getchar();
	return s*w;
}

void init(){
	f[1][1][1]=1;f[1][0][0]=1;
	for(int i=2;i<35;i++)
		for(int j=0;j<=1;j++)
			for(int k=0;k<=i;k++)
				for(int s=0;s<=1;s++){
					if(!j)f[i][j][k]+=f[i-1][s][k];
					if(j&&k) f[i][j][k]+=f[i-1][s][k-1];
				}
}

int solve(int x){
	memset(a,0,sizeof a);
	int len=0,ans=0,cnt1=1,cnt0=0;
	while(x){a[++len]=x%2;x/=2;}
	for(int i=len-1;i>=1;i--){
		if(a[i]) for(int j=0;j<=len/2-cnt1;j++) ans+=f[i][0][j];
		cnt1+=a[i];cnt0+=(a[i]==0);
		if(cnt0>=cnt1&&i==1) ans++;
	}
	for(int i=1;i<len;i++)
		for(int j=0;j<=i/2;j++)
		    ans+=f[i][1][j];
	return ans;
}

int main(){
	init();
	l=read();r=read();
	printf("%d",solve(r)-solve(l-1));
	return 0;
}

原文地址:https://www.cnblogs.com/KnightL/p/14144104.html