bzoj3780 数字统计
题意:
将[L..R]中的所有整数用M位二进制数表示(允许出现前导0)现在将这些数中的每一个作如下变换:
从这个数的最低两位开始,如果这两位都是0,那么X=1,否则X=0。现在将这两位删去,然后将X放在原来最低位的位置上。重复这个变换直到这个数只剩下一位为止。
例如01001的变换过程:01001-->0100-->011-->00-->1。问[L,R]中多少个数变换后值为Y(Y为0或1),用无前导零的二进制数输出
1<=M<=200,1<=数据组数<=50。
这道题的性质很特殊所以可以用一些机智的方法过掉,但懒于观察性质就可以写数位DP.
关于数位DP:
一般是要求将数字视为字符串并统计信息,并通常要求计算一段数字区间的信息的和的问题。
数据范围常能达到 10^9 或者 10^18。
特点:思想一般比较简单但是细节较多,不对拍基本上是要完.
数位DP常见的是在十进制数字串上进行,本题是二进制串,也可使用相同的思路.
首先区间查询可以转化为前缀和相减,我们只需要解决[0,L-1]和[0,R]这两个区间的答案即可.
那么问题变成有多少小于等于x的数满足合并的最终结果为0/1
对于等于x的情况我们特判一下,接下来考虑小于x的情况.如果一个数y小于x那么必然有一位和x不同,且一定是x的这一位为1,y的这一位为0.而且,y在这个位置后面的数位随意填写都可以满足y<x
例如,x为01101,y的前三位为010,那么y后面的两位任意填写都可以使y<x
那么我们枚举y从左到右第一个和x不同的位置在哪里.这个位置必须满足x的这一位是1且y的这一位是0.假如后面还有i个位置,那么这i个数位可以随意填写,我们可以预处理出有多少个长度为i的二进制串合并之后得到的结果为0/1,记为f[i][0]和f[i][1].(预处理的时候需要加一维才能转移,考虑一下)
对于左侧的数位,y和x是一样的,我们可以预处理出x的左边i位在右边加上一个0/1之后合并得到的结果,然后就可以DP了.写起来会有一些细节,一定要对拍.
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn=205; struct ll{ int num[maxn];int len; ll(){ len=0;memset(num,0,sizeof(num)); } ll(int x){ memset(num,0,sizeof(num));len=1;num[1]=x; while(num[len]>=2){ num[len+1]=num[len]/2;num[len]%=2;len++; } } ll operator +(const ll &B)const{ ll C;C.len=max(len,B.len); for(int i=1;i<=C.len;++i)C.num[i]=num[i]+B.num[i]; for(int i=1;i<=C.len;++i){ if(C.num[i]>=2){ C.num[i+1]+=C.num[i]/2;C.num[i]%=2; if(i==C.len)C.len++; } } return C; } ll operator -(const ll &B)const{//assert:C>0 ll C;C.len=len; for(int i=1;i<=len;++i)C.num[i]=num[i]-B.num[i]; for(int i=1;i<=len;++i){ if(C.num[i]<0){ C.num[i]+=2;C.num[i+1]--; } } while(C.num[C.len]==0&&C.len>=1)C.len--; return C; } void output(){ if(len==0){ printf("0");return; } for(int i=len;i>=1;--i)printf("%d",num[i]); } void operator +=(const ll &B){ (*this)=(*this)+B; } }ANS; int m,ans; char str1[maxn],str2[maxn]; int g[maxn][2];ll f[maxn][2][2]; inline int calc(const int &a,const int &b){ return (a==b)&&(a==0); } ll dp(char s[]){ g[0][0]=0;g[0][1]=1; for(int i=0;i<m;++i){ g[i+1][0]=g[i][calc(0,s[i]-'0')];g[i+1][1]=g[i][calc(1,s[i]-'0')]; } memset(f,0,sizeof(f)); f[1][0][0]=f[1][1][1]=ll(1); for(int i=1;i<m;++i){ f[i+1][0][1]+=f[i][0][0];f[i+1][0][1]+=f[i][1][0]; f[i+1][0][0]+=f[i][0][1];f[i+1][0][0]+=f[i][1][1]; f[i+1][1][0]+=f[i][0][0];f[i+1][1][0]+=f[i][1][0]; f[i+1][1][0]+=f[i][0][1];f[i+1][1][0]+=f[i][1][1]; //f[i+1][0][1].output();printf("\n"); } //f[2][0][0].output();printf("\n");f[2][1][0].output();printf("\n"); ll Ans=0; for(int i=0;i<m;++i){ if(i==m-1){ if(g[i][s[i]-'0']==ans){Ans+=ll(1);} if(s[i]=='1'&&g[i][0]==ans){Ans+=ll(1);} } else if(s[i]=='1'){ if(g[i][calc(0,0)]==ans){//printf("A%d %d\n",i,g[i][0]);printf("pre:");Ans.output();printf("\n"); Ans=Ans+f[m-i-1][0][0];Ans=Ans+f[m-i-1][1][0];//printf("after:");Ans.output();printf("\n"); } if(g[i][calc(0,1)]==ans){//printf("B%d\n",i);printf("pre:");Ans.output();printf("\n"); Ans=Ans+f[m-i-1][0][1];Ans=Ans+f[m-i-1][1][1];//printf("after:");Ans.output();printf("\n"); } } } return Ans; } bool check(char s[]){ int t=s[m-1]-'0'; for(int i=m-2;i>=0;--i)t=calc(t,s[i]-'0'); return t==ans; } int main(){ // freopen("count.in","r",stdin); // freopen("count.out","w",stdout); int t;scanf("%d",&t); while(t--){ scanf("%d%d",&m,&ans); scanf("%s%s",str1,str2); ANS=dp(str2)-dp(str1); if(check(str1))ANS=ANS+ll(1); ANS.output();printf("\n"); }//while(1); // fclose(stdin);fclose(stdout); return 0; }