题目链接: 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());
}
}
好像这题这样写几乎变成了纯板子题。但是打铁选手也想补题啊