题解 CF1670F【Jee, You See?】
problem
给出常数 \(n,z\),令 \(F(m)\) 表示所有满足以下条件的数列 \(\{a_i\}\) 的数量:
- \(\{a_i\}\) 有 \(n\) 项,都是非负整数。
- 它们的和不大于 \(m\)。
- 它们的异或和恰好为常数 \(z\)。
又给出 \(l,r\),求出 \(F(r)-F(l-1)\)。数据范围 \(n\leq 2^{10},0\leq m,z<2^{60}\)。
数位 DP 的顺序
常见的数位 DP 有两种顺序:从低到高、从高到低。它们并不完全一样。
以最简单的问题:求出有多少个非负整数 \(x\) 满足 \(x\leq m\) 为例。
从高到低(第 \(d\) 位表示从高到低第 \(d\) 位)
这一种写法可以用 \((d,done)\) 表示一个状态,它表示:
- \(x\) 的高 \((d-1)\) 位已经填完。现在填第 \(d\) 位。
- 如果 \(done\) 为假,
- 说明这个数字的高 \((d-1)\) 位和 \(m\) 的高 \((d-1)\) 位完全一致(位数不同时补零)。
- 当前的第 \(d\) 位,需要满足 \(x\leq m\)。
- 如果 \(done\) 为真,
- 说明前面有一位满足 \(x<m\),\(d\) 以及后面的位无论如何填都能满足 \(x\leq m\) 的限制。
- 当前的第 \(d\) 位可以随便填了。
转移也扯两句:
- 初始状态为 \((n,0)\)。从 \((d,done)\) 转移至 \((d-1,done')\),直至 \(d=0\)。
- 如果 \(done=0\) 且填入 \(x<m\),则使 \(done'=1\)。其它情况 \(done'=done\)。
伪代码:
const int B=2;//进制
int conbit(int k){/*得到 m 的第 k 位*/}
LL f[60][2];//memset 为 -1
LL dfs(int d,bool done){
if(!d) return 1;
if(~f[d][done]) return f[d][done];
LL ans=0;
for(int i=0;i<=done?B-1:conbit(d);i++){
ans+=dfs(d-1,done||i<conbit(d));
}
return f[d][done]=ans;
}
一个小小的 trick:如果这样的题多测,可以不用清空 \(done=1\) 时的数组。因为这时与 \(m\) 无关。
从低到高(第 \(d\) 位表示从低到高第 \(d\) 位)
这一种写法可以用 \((d,lim)\) 表示一种状态,他表示:
- 当前后 \((d-1)\) 位已经完成,要填第 \(d\) 位。
- \(lim=1\) 表示从现在填完的东西来看,\(x\leq m\);\(lim=0\) 表示现在 \(x>m\)。
- 最终我们只取 \(lim=1\) 的答案。
转移:
- 从 \((d,lim)\) 转移到 \((d+1,lim')\)。
- 对于第 \(d\) 位,若 \(lim=1\),将 \(lim'=[x\leq m]\);若 \(lim=0\),将 \(lim'=[x<m]\)。建议手推一下。
代码:
LL dfs(int d,bool lim){
if(d==60) return lim;
if(~f[d][lim]) return f[d][lim];
LL ans=0;
for(int i=0;i<B;i++){
ans+=dfs(d+1,lim?i<=conbit(m,d):i<conbit(m,d));
}
return f[d][done]=ans;
}
区别
- 从低到高的写法,不关心高位是什么,也不关心是否超过限制,可以用来做诸如进位的题目。
- 从高到低的写法,保证所填数一定是满足条件的,且不(能)关心我的低位填了什么。
solution
如果我们逐个确定 \(a_i\),复杂度会升天;考虑换种思路,同时枚举所有 \(a_i\) 的一位。因为这里是异或,那么我们可以用二进制。
因为我们这里有一个加法要进位,考虑从低到高枚举每一位,记录下后面的进位,然后数位 DP。
分析一下进位的范围:
- 一共 \(n\) 个数字,假如当前在第 \(d\) 位。
- 则对于下一位,我们最多进位 \(\sum_{i=0}^d \frac{n}{2^{d-i+1}}=n\sum_{i=1}^{d+1}2^{-i}\),这东西肯定不会超过 \(n\),所以我们进位的范围最多为 \(n\)。
- 我们还可以归纳:假如进位最多进 \(n\) 个,则最低位满足归纳假设,对于某一位它最多是 \(\frac{n+n}{B}\) 也就是 \(\frac{2}{B}n\),因为进制 \(B\geq 2\),所以归纳成立。
那么直接做就好了,复杂度 \(O(n^2\log z)\)。
code
点击查看代码
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr,##__VA_ARGS__)
#else
#define debug(...) void(0)
#endif
typedef long long LL;
const int P=1e9+7;
//本题取模很多,请注意常数优化
LL mod(LL x){return x%P;}
void red(LL&x){x=mod(x);}
LL getbit(int k){return 1ll<<k;}
bool conbit(LL x,int k){return x&getbit(k);}
LL qpow(LL a,LL b,int p=P){LL r=1;for(a%=p;b;b>>=1,a=a*a%p) if(b&1) r=r*a%p; return r;}
int n;
LL z,f[61][2][1<<10];
LL C[1<<10][1<<10];
//记忆化写法,写丑了会 TLE
//LL dfs(int d,bool lim,int ad){//ad<=1000
// if(d==60) return lim&&!ad;//有进位就肯定是假的
// if(~f[d][lim][ad]) return f[d][lim][ad];
// LL ans=0;
// for(int i=0;i<=n;i++){//有 i 个 1 在这一位
// if(i%2!=conbit(z,d)) continue;
// int sum=ad+i,dit=sum&1;
// red(ans+=C[n][i]*dfs(d+1,lim?dit<=conbit(m,d):dit<conbit(m,d),sum>>1));
// }
// return f[d][lim][ad]=ans;
//}
//LL solve(LL m0){
// memset(f,-1,sizeof f);
// m=m0;
// return dfs(0,1,0);
//}
LL dp(LL m){
memset(f,0,sizeof f),f[60][1][0]=1;
for(int d=59;d>=0;d--){
int zd=conbit(z,d),md=conbit(m,d);
for(int lim=0;lim<=1;lim++){
for(int ad=0;ad<1<<10;ad++){
for(int i=0;i<=n;i++){
if((i&1)!=zd) continue;
int sum=ad+i;
red(
f[d][lim][ad]
+=C[n][i]*f[d+1][lim?(sum&1)<=md:(sum&1)<md][sum>>1]
);
}
}
}
}
debug("dp(%lld)=%lld\n",m,f[0][1][0]);
return f[0][1][0];
}
int main(){
for(int i=0;i<1<<10;i++){
C[i][0]=1;
for(int j=1;j<=i;j++) C[i][j]=(C[i-1][j]+C[i-1][j-1])%P;
}
// #ifdef LOCAL
// freopen("input.in","r",stdin);
// #endif
LL l,r;
scanf("%d%lld%lld%lld",&n,&l,&r,&z);
printf("%lld\n",mod(dp(r)-dp(l-1)+P));
return 0;
}
本文来自博客园,作者:caijianhong,转载请注明原文链接:https://www.cnblogs.com/caijianhong/p/solution-CF1670F.html