[BZOJ4513] [SDOI2016] 储能表 - 数位DP

数位DP博大精深阿弥陀佛。

(回归正题)woc这取模要死,乘法到处爆炸,一不小心就炸longlong,考试肯定fst惨挂只有暴力分。

对于子问题F(n,m,k)求∑[0<=i<n]∑[0<=j<m] [i xor j<=k] i xor j

首先将n和m自减后同k一起二进制分解,统一长度后按从高位到低位排,然后我们就可以DP了。

这里DP[t][q][k][y][p]表示:

t表示当前进行到的位数,即这个子问题求解到了分解出的二进制的前t位。

当p=0,这个值表示子问题中,满足i xor j<=k[1...t]的总的二元组(i,j)的个数

当p=1表示这些所有的二元组(i,j)的i xor j之和

当q=1,表示被限定为i=n[1...t],否则i<n[1...t];当k=1,表示被限定为j=m[1...t],否则j<m[1...t]

当y=1,限定i xor j=k[1...t],否则i xor j<k[1...t]

这样我们考虑从DP[i-1][j][k][t][p]向上转移

显然我们分别考虑这一次对于i和j在末尾的选择x和y,就可以得出新的newj,newk和newt,讨论一下即可。

然后我们的值转移显然是

DP[i][newj][newk][newt][0]+=DP[i-1][j][k][t][0]

DP[i][newj][newk][newt][1]+=DP[i-1][j][k][t][1]+(x xor y)DP[i-1][j][k][t][0]

根据容斥原理,我们只需要算出A(x1,y1)=F(n,m,max(n,m)*2(使位数更多))和B(x2,y2)=F(n,m,k),(x和y分别表示方案数和和)那么

ans=y1-k*x1+k*x2-y2

#include"bits/stdc++.h"
using namespace std;
typedef long long ll;

ll n,m,k;int P;
const int N=65;
ll dp[N][2][2][2][2];
int fa[N],fb[N],fc[N],l;

int C;

inline void ADD(ll&x,ll y){(x+=y)%=P;}

int makedp(ll n,ll m,ll k,int P){
	memset(fa,0,sizeof(fa));memset(fb,0,sizeof(fb));
	memset(fc,0,sizeof(fc));memset(dp,0,sizeof(dp));
	int i,j,t,a,b,p;n--,m--; dp[0][1][1][1][0]=1;
	for(i=1,j=0;n;n>>=1,i++)fa[i]=n%2;l=max(l,i-1);
	for(i=1,j=0;m;m>>=1,i++)fb[i]=m%2;l=max(l,i-1);
	for(i=1,j=0;k;k>>=1,i++)fc[i]=k%2;l=max(l,i-1);
	for(i=1;2*i<=l;i++){
		swap(fa[i],fa[l-i+1]);
		swap(fb[i],fb[l-i+1]);
		swap(fc[i],fc[l-i+1]);
	}
	for(i=1;i<=l;i++){
		for(j=0;j<=1;j++){
			for(a=0;a<=1;a++){
				for(b=0;b<=1;b++){
					if(dp[i-1][j][a][b][0]==0) continue;
					for(t=0;t<=1;t++)for(p=0;p<=1;p++){
						if((t^p)>fc[i]&&b||t>fa[i]&&j||p>fb[i]&&a) continue;
						ADD(dp[i][j&&t==fa[i]][a&&p==fb[i]][b&&(t^p)==fc[i]][0],dp[i-1][j][a][b][0]);
						ADD(dp[i][j&&t==fa[i]][a&&p==fb[i]][b&&(t^p)==fc[i]][1],dp[i-1][j][a][b][1]*2+(t^p)*dp[i-1][j][a][b][0]);
					}
				}
			}
		}
	}
	return l;
}

void work(){
	scanf("%lld%lld%lld%d",&n,&m,&k,&P);
	ll ans=0,sum=0;int i,j,l,t;l=makedp(n,m,max(n,m)<<1,P);
	for(i=0;i<=1;i++)for(j=0;j<=1;j++)
		ans=(ans+dp[l][i][j][0][1])%P;
	l=makedp(n,m,k,P);ans=((ans-n%P*(m%P)%P*(k%P))%P+P)%P;
	for(i=0;i<=1;i++)for(j=0;j<=1;j++)for(t=0;t<=1;t++)
		ans=((ans+k%P*(dp[l][i][j][t][0]%P)%P-dp[l][i][j][t][1])%P+P)%P;
	printf("%lld\n",ans);
}

int main(){
	int T;cin>>T;
	while(T--)work();
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值