bzoj4513 [Sdoi2016]储能表 dp

这种数位dp第一次见。。

其实应该是利用位运算相互独立来避免后效性

一般的数位dp只有一个范围 这个题有三个范围。。

由于数位和整个数的大小没直接关系,所以就需要用状态记录

首先不合法的一定不转移,对于n和m的限制 ,一位合法的话有可能出现后面无论如何都合法, 与后面的数不能小于剩下的数,由于比他大的都不合法了,所以剩下的一定是与n、m前面几位相同的

所以就设计状态1表示前面几位都与n/m/k相同  0表示不同且合法(即后面的就都合法了)

比较暴力的dp。

注意%p的时候k本身要%p。。

码:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define ll long long
ll er[76],n,m,ans,K,p,f[76][2][2][2],i1,i2,g[76][2][2][2];
int T,i,j,k,l;
int main()
{
	er[0]=1;
	for(i=1;i<=63;i++)
	er[i]=er[i-1]*2;
	scanf("%d",&T);
	while(T--)
	{ans=0;
	memset(f,0,sizeof(f));
	memset(g,0,sizeof(g));
	scanf("%lld",&n);
	n--;
	scanf("%lld",&m);
	m--;
	scanf("%lld",&K);	
	scanf("%lld",&p);	
	f[63][1][1][1]=1;	
	for(i=62;i>=0;i--)
	{
	for(j=0;j<=1;j++)
	for(k=0;k<=1;k++)
	for(l=0;l<=1;l++)
	{
		for(i1=0;i1<=1;i1++)
		for(i2=0;i2<=1;i2++)
		{
			int mbj,mbk,mbl;			
			if(j==1&&(!(n&er[i]))&&i1)	continue; //是0  
			if(k==1&&(!(m&er[i]))&&i2)	continue;   //是0
		 	if(l==1&&(K&er[i])&&(!(i1^i2)))continue;  //是1  
		    if(j==1&&(!(n&er[i]))&&(!i1))mbj=1;
		    if(k==1&&(!(m&er[i]))&&(!i2))mbk=1;
		    if(l==1&&(!(K&er[i]))&&(!(i1^i2)))mbl=1;
			if(j==1&&(n&er[i])&&i1)mbj=1;
			if(k==1&&(m&er[i])&&i2)mbk=1;	
			if(l==1&&(K&er[i])&&(i1^i2))mbl=1;
			if(j==1&&(n&er[i])&&(!i1))mbj=0;
			if(k==1&&(m&er[i])&&(!i2))mbk=0;				  
			if(l==1&&(!(K&er[i]))&&(i1^i2))mbl=0; 
			if(j==0)mbj=0;
			if(k==0)mbk=0;
			if(l==0)mbl=0;
			f[i][mbj][mbk][mbl]+=f[i+1][j][k][l];
			f[i][mbj][mbk][mbl]%=p;
			g[i][mbj][mbk][mbl]+=((i1^i2)*er[i]%p*f[i+1][j][k][l]%p+g[i+1][j][k][l])%p;
			g[i][mbj][mbk][mbl]%=p;
		}	
	}
	}
for(i=0;i<=1;i++)for(j=0;j<=1;j++)for(k=0;k<=1;k++)ans-=(f[0][i][j][k]*(K%p))%p,ans%=p,     ans+=g[0][i][j][k],ans%=p;  
printf("%lld\n",(ans+p)%p);
    }
}

猜你喜欢

转载自blog.csdn.net/haobang866/article/details/79388383
今日推荐