题解 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;
}


posted @ 2023-01-27 18:02  caijianhong  阅读(40)  评论(0编辑  收藏  举报