P4067 [SDOI2016] 储能表 题解

P4067

因为不能直接减去 nmk,先把题目中的式子转化为求 ijk 的数的个数与和。

这样就可以进行数位 dp 了。令 fbt,un,um,lk 表示当前考虑到第 bit 位,n 有没有达到上界,m 有没有达到上界,k 有没有达到下界的个数,g 表示的是这些数的总和。然后因为考虑的是有没有到达上/下界,从高向低位进行记搜更简单。

转移是简单的,即 fbit,un,um,lk=fbit1,un,um,lkgbit,un,um,lk=gbit1,un,umlk+(ij)×2bit×fbit1,un,um,lk。其中 i,j 表示行、列的当前枚举的位的值。

为了实现方便,返回值是一个 pair 即可只用一次记搜。

代码:

typedef long long ll;
typedef pair<int,int> pii;
ll n,m,k;int p;
ll pw[65];
pii f[64][2][2][2];int vis[64][2][2][2];
pii dfs(int bit,int un,int um,int lk){
	if(bit==-1)return pii(1,0);
	if(vis[bit][un][um][lk])return f[bit][un][um][lk];
	vis[bit][un][um][lk]=1;
	pii &now=f[bit][un][um][lk];
	now=pii(0,0);
	int bn=n>>bit&1,bm=m>>bit&1,bk=k>>bit&1;
	for(int i=0;i<=(un?bn:1);i++)for(int j=0;j<=(um?bm:1);j++){
		if(lk&&(i^j)<bk)continue;
		pii res=dfs(bit-1,un&(i==bn),um&(j==bm),lk&((i^j)==bk));
		now.first=(now.first+res.first)%p,now.second=(now.second+res.first*(i^j)*1ll*pw[bit]+res.second)%p;
	}
	return now;
}
void solve(){memset(vis,0,sizeof(vis));
	scanf("%lld%lld%lld%d",&n,&m,&k,&p);n--,m--;
	pw[0]=1;for(int i=1;i<=60;i++)pw[i]=pw[i-1]*2%p;
	pii res=dfs(60,1,1,1);
	cout<<(res.second-res.first*1ll*(k%p)%p+p)%p<<"\n";
}
int main(){
	int T;cin>>T;
	while(T--)solve();
    return 0;
}
posted @   Pengzt  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
点击右上角即可分享
微信分享提示