zoukankan      html  css  js  c++  java
  • LOJ 6274 数字(数位dp)

    LOJ 6274 数字(数位dp)

    题目描述

    NiroBC 姐姐脑洞了两个数字 和 ,它们满足 ,且 , NiroBC 姐姐想知道 有多少种不同的取值,若有多组 的 值相同,则只算一次。

    (其中 表示按位取或,C/C++中写作|Pascal中写作or

    (其中 表示按位取与,C/C++中写作&Pascal中写作and

    输入格式

    一行,五个非负整数 。

    输出格式

    一行,一个整数,答案。

    样例

    样例输入

    11 3 10 8 13
    

    样例输出

    7
    

    数据范围与提示

    对于所有数据,\(T,L_x,R_x,L_y,R_y<2^{60}\)

    Subtask1

    \(T,L_x,R_x,L_y,R_y<2^{10}\)

    枚举统计答案即可

    namespace pt1{
    	int mk[1<<10];
    	void Solve(){
    		int ans=0;
    		rep(i,Lx,Rx) rep(j,Ly,Ry) if((i|j)==T) mk[i&j]=1;
    		rep(i,0,1023) ans+=mk[i];
    		printf("%d\n",ans);
    	}
    }
    

    $$\ $$

    Subtask2

    \(L_x=L_y=0\)

    考虑简单数位\(dp\)

    \(dp[i][lim1][lim2]\)表示\(\text{dp}\)到了第\(i\)位,\(lim1,lim2\)表示是否受到仍\(R_x,R_y\)的限制

    枚举两个数这一位分别选择\(0,1\),是否与\(T\)的这一位相同

    但是存在一种会算重复的情况

    当两个数均可以取\(0,1\),且\(T_i=1\)的情况,则存在\((0,1),(1,0)\)两种方案且他们的与相同

    如何处理重复?我们分类讨论一下(看不下去可以直接看结论)

    1.\(lim1=1,lim2=1\)

    这时候,把\(R_x,R_y\)中剩下的位数较大的那一个选为1,另一个选为0,这样选择一定包含反过来的情况,因为限制减轻了

    2.\(lim1=1,lim2=0\)

    当然是取(0,1),理由相同

    3.\(lim1=0,lim2=1\)

    当然是取(1,0),理由相同

    4.\(lim1=0,lim2=0\)

    那么可以随便取其中一者

    最终发现:出现重复状态时,取其中\(\text{dp}\)值较大的下一步状态即可

    namespace pt2{
    	ll A[70],Ac,B[70],Bc;
    	ll dp[70][2][2];
    
    	ll dfs(int p,int lim1,int lim2){
    		if(p==0) return 1;
    		if(~dp[p][lim1][lim2]) return dp[p][lim1][lim2];
    		ll res=0,a=lim1?A[p]:1,b=lim2?B[p]:1;
    		rep(i,0,a) rep(j,0,b) if((i|j)==T[p]) res+=dfs(p-1,lim1&&i==a,lim2&&j==b);
    		if(a==1 && b==1 && T[p]==1) res-=min(dfs(p-1,lim1,0),dfs(p-1,0,lim2));
             // 减去较小的,就是取较大的
    		return dp[p][lim1][lim2]=res;
    	}
    
    	ll Calc(ll n,ll m){
    		Ac=Bc=0;
    		memset(A,0,sizeof A),memset(B,0,sizeof B);
    		while(n) A[++Ac]=(n&1),n>>=1;
    		while(m) B[++Bc]=(m&1),m>>=1;
    		memset(dp,-1,sizeof dp);
    		return dfs(max(Tc,max(Ac,Bc)),1,1);
    	}
    	void Solve(){ printf("%lld\n",Calc(R1,R2)); }
    }
    

    \[\ \]

    \[\ \]

    Subtask3

    设两个数分别取\(x,y\),且\(x\and y=i\),枚举\(i\),然后再枚举\(x\),就能得到计算得出\(y\),检查每一个\(i\)是否存在方案

    复杂度为\(O(3^{|T|})\)

    namespace pt3{
    	void Solve(){
    		int ans=0;
    		for(ll i=Tx;;i=(i-1)&Tx) {
    			int fl=0;
    			for(ll j=(Tx^i);;j=(j-1)&(Tx^i)) {
    				ll x=j|i,y=Tx+i-x;
    				assert((x|y)==Tx && (x&y)==i);
    				if(x>=L1 && x<=R1 && y>=L2 && y<=R2) {
    					fl=1;
    					break;
    				}
    				if(!j) break;
    			}
    			ans+=fl;
    			if(!i) break;
    		}
    		printf("%d\n",ans);
    	}
    }
    
    

    \[\ \]

    Subtask4

    \(Subtask2\)一样,都可以通过枚举发现同样的结论,所以只需要给\(\text{dp}\)状态加上两维即可AC

    (事实是不会证明,但是对拍了20000组随机数据,跑过了<64的所有数据,应该没有问题)

    namespace pt4{
    	ll A[70],Ac,B[70],Bc,C[70],Cc,D[70],Dc;
    	ll dp[70][2][2][2][2];
    
    	ll dfs(int p,int lim1,int lim2,int lim3,int lim4){
    		if(p==0) return 1;
    		if(~dp[p][lim1][lim2][lim3][lim4]) return dp[p][lim1][lim2][lim3][lim4];
    		ll res=0;
    		int a=lim1?A[p]:0,b=lim2?B[p]:1;
    		int c=lim3?C[p]:0,d=lim4?D[p]:1;
    
    		rep(i,a,b) rep(j,c,d) if((i|j)==T[p]) 
                res+=dfs(p-1,lim1&&i==a,lim2&&i==b,lim3&&j==c,lim4&&j==d);
    		if(a==0 && b==1 && c==0 && d==1 && T[p]==1) {
    			res-=min(dfs(p-1,lim1&&0==a,lim2&&0==b,lim3&&1==c,lim4&&1==d),
    			dfs(p-1,lim1&&1==a,lim2&&1==b,lim3&&0==c,lim4&&0==d));
    		}
    		return dp[p][lim1][lim2][lim3][lim4]=res;
    	}
    
    	ll Calc(){
    		Ac=Bc=0;
    		while(L1) A[++Ac]=(L1&1),L1>>=1;
    		while(R1) B[++Bc]=(R1&1),R1>>=1;
    		while(L2) C[++Cc]=(L2&1),L2>>=1;
    		while(R2) D[++Dc]=(R2&1),R2>>=1;
    		memset(dp,-1,sizeof dp);
    		ll l=0;
    		cmax(l,Ac),cmax(l,Bc),cmax(l,Cc),cmax(l,Dc),cmax(l,Tc);
    		return dfs(l,1,1,1,1);
    	}
    	void Solve(){ printf("%lld\n",Calc()); }
    }
    

    正规Solution(进一步状压了状态)

    前面的转移中总是会出现重复,这里我们通过改变dp状态来防止这种重复

    每次把可能重复的转移全部存下来

    即如果存在两个转移方案\(x \and y\)相同,我们就把转移他们得到的下一步状态压进去,得到一个新的dp

    \(dp[i][S]\)表示可能出现的\((lim1,lim2,lim3,lim4)\)组的集合为\(S\)

    每次把相同的\(x\and y\)压进同一个状态里,就防止了重复

    ll T,L1,R1,L2,R2;
    ll dp[61][1<<16];
    int Get(int a,int b,int c,int d){ return a|b<<1|c<<2|d<<3; } // 为四个lim1的情况编号
    
    int main(){
    	T=rd<ll>(),L1=rd<ll>(),R1=rd<ll>(),L2=rd<ll>(),R2=rd<ll>();
    	dp[60][1<<Get(1,1,1,1)]=1; // 初始的四个lim均为1
    	drep(i,59,0) {
    		int l1=L1>>i&1,r1=R1>>i&1,l2=L2>>i&1,r2=R2>>i&1,t=T>>i&1;
    		rep(S,0,(1<<16)-1) if(dp[i+1][S]) {
    			int G[2]={0,0};
    			rep(a,0,1) rep(b,0,1) rep(c,0,1) rep(d,0,1) if(S&(1<<Get(a,b,c,d))) { // 枚举四个lim
    				rep(x,a?l1:0,b?r1:1) rep(y,c?l2:0,d?r2:1) { // 枚举可行的x,y
    					if((x|y)!=t) continue;
    					G[x&y]|=1<<Get(a&&x==l1,b&&x==r1,c&&y==l2,d&&y==r2); 
                          // 把相同的x&y压在一起,防止了重复
    				}
    			}
    			rep(j,0,1) if(G[j]) dp[i][G[j]]+=dp[i+1][S];
    		}
    	}
    	ll ans=0;
    	rep(i,0,(1<<16)-1) ans+=dp[0][i];
    	printf("%lld\n",ans);
    }
    
    
  • 相关阅读:
    关于dedecms数据量大以后生成目录缓慢的问题解决
    火车头采集器对织梦后台管理员永久登录的问题解决办法
    谈谈嵌入式程序员的发展方向
    我的愉快的备案经历-阿里云备案
    百度更新趋向稳定,我的计划要重新开始
    网站被攻击的常见方式和解决办法
    一篇关于营销的功能需求分析
    到了一定程度,就是如何书写需求的时候
    你是零基础,该如何经营好一个网站
    信用卡如何正确养卡提高额度
  • 原文地址:https://www.cnblogs.com/chasedeath/p/12743841.html
Copyright © 2011-2022 走看看