2020 ICPC 济南站 L:Bit Sequence (数位dp)

题目链接: link.

分析

题目要求“the number of integer … satisfies ”,即"满足要求的数"的个数。十有八九都是数位dp。
与简单的数位dp不同,这题满足要求的数的状态相对难想。但判断一个数是否满足要求不难,时间复杂度O(m),考虑如何统一一下满足要求数的状态。
首先知道数的二进制表示1的个数的奇偶肯定影响结果。
考虑到m较小,如果不考虑进位,x+m最多影响到较低的6位二进制。
如果考虑进位,那么7位和更高位的二进制在+m的时候最多影响1次,也就是由进位影响的。进位会改变连续的一段1,进位最多一次,这连续的一段都会被反转从而影响 f ( x + m ) f(x+m) f(x+m)的结果。
小结一下,一个数的状态有:
① 低6位的全部结果
② 全部的1的个数的奇偶
③ 第7位到更高位连续的1的个数的奇偶
想好这些状态就可以愉快的套模板了。
数位dp的dfs版,前两个状态当然是当前位和是否需要考虑上界,剩下的参数都是一个数的状态。

#include<iostream>
#include<algorithm>
#include<cstring>
#define rep(i,a,b) for(int i=a;i<b;i++)
#define mem(a,b) memset(a,b,sizeof(a))
using ll=long long;
using namespace std;
ll dp[64][2][128][2][2]={},x;
int bi[64]={},am[109]={},m;
ll calc(int i,int s,int t){
	int f=1;
	for(int j=0;j<m&&f;j++)
		if(i+j<128) f&=(__builtin_parity(i+j)^s)==am[j];
		else f&=(__builtin_parity(i+j)^s^t)==am[j];
	return f;
}
ll dfs(int pos,int lim,int sta,int s,int t){
	if(pos==-1) return calc(sta,s,t);
	ll&res=dp[pos][lim][sta][s][t];
	if(res!=-1) return res;
	res=0;
	int up=lim?bi[pos]:1;
	for(int i=0;i<=up;i++) 
		if(pos>6) 
			res+=dfs(pos-1,lim&&i==up,(sta*2+i)&127,s^i,i&(!t));
		else res+=dfs(pos-1,lim&&i==up,(sta*2+i)&127,s,t);
	return res;
}
ll solve(){
	mem(dp,-1);
	int len=0;
	for(ll xi=x;xi;xi>>=1) bi[len++]=xi&1;
	return dfs(len-1,1,0,0,0);
}
int main() {
	int ___;
	for(scanf("%d",&___);___--;){
		scanf("%d%lld",&m,&x);
		rep(i,0,m) scanf("%d",&am[i]);
		printf("%lld\n",solve());
	}
}

可以发现这个代码运行时间比较长,238ms,因为dp的状态太多了,每次memset的时候消耗了大量时间。我们可以考虑优化一下状态,可以发现状态①和高位没有关系,所以可以dfs搜索到第6位以后直接计算。

#include<iostream>
#include<algorithm>
#include<cstring>
#define rep(i,a,b) for(int i=a;i<b;i++)
#define mem(a,b) memset(a,b,sizeof(a))
using ll=long long;
using namespace std;
ll dp[64][2][2][2]={},x;
int bi[64]={},am[109]={},m;
ll calc(int lim,int s,int t){
	int res=0,hi=lim?x%128:127;
	for(int i=0;i<=hi;i++){//枚举低6位
		int f=1;
		for(int j=0;j<m&&f;j++)//O(m)逐一检查,注意到模2意义下加减都相当于异或
			if(i+j<128) f&=(__builtin_parity(i+j)^s)==am[j];
			else f&=(__builtin_parity(i+j)^s^t)==am[j];
		res+=f;
	}
	return res;
}
ll dfs(int pos,int lim,int s,int t){
	ll&res=dp[pos][lim][s][t];
	if(res!=-1) return res;
	if(pos<=6) return res=calc(lim,s,t);
	res=0;
	int up=lim?bi[pos]:1;
	for(int i=0;i<=up;i++) //注意到模2意义下加减都相当于异或
		res+=dfs(pos-1,lim&&i==up,s^i,i&(!t));
	return res;
}
ll solve(){
	mem(dp,-1);
	int len=0;
	for(ll xi=x;xi;xi>>=1) bi[len++]=xi&1;
	return dfs(len-1,1,0,0);
}
int main() {
	int ___;
	for(scanf("%d",&___);___--;){
		scanf("%d%lld",&m,&x);
		rep(i,0,m) scanf("%d",&am[i]);
		printf("%lld\n",solve());
	}
}

好像这题这样写几乎变成了纯板子题。但是打铁选手也想补题啊

  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值