洛谷 P7620 - CF1431J Zero-XOR Array(状压 dp)

洛谷题面传送门

首先显然题目等价于求有多少个长度 \(n-1\) 的序列 \(b\) 满足 \(a_i\le b_i\le a_{i+1}\),满足 \(b_1\oplus b_2\oplus\cdots\oplus b_{n-1}=a_1\oplus a_2\oplus\cdots\oplus a_n=S\)

考虑入手解决这个问题,注意到 \(n\) 数据范围很小,只有 \(18\),而值域非常大,高达 \(2^{60}\),因此可以考虑拆位后 \(2^n\) 枚举个什么状态后计算。但是究竟枚举什么状态呢?我们不妨先来分析一些性质:

Observation 1. 对于某个 \(b_i\) 的第 \(j\) 位,总共有 \(3\) 种可能:

  1. \(b_i\) 的第 \(j\) 位到第 \(m-1\) 位(最高位)组成的前缀与 \(a_i\) 对应前缀相同
  2. \(b_i\) 的第 \(j\) 位到第 \(m-1\) 位组成的前缀不同于 \(a_i\),但与 \(a_{i+1}\) 相同
  3. \(b_i\) 的第 \(j\) 位到第 \(m-1\) 位组成的前缀既不同于 \(a_i\),也不同于 \(a_{i+1}\)

也就是将每个 \(b_i\) 的每一段前缀分为达到上界、达到下界和即每达到上界也没达到下界三种情况。

但是知道这个性质之后有什么用吗?

Observation 2. 对于某个 \(b_i\) 的某个 \(j\),如果它满足上面的第三种情况,并且我们已经固定了每个 \(b_i\)\(j\) 位到第 \(m-1\) 位的取值,那么记 \(l_x\)\(b_x\) 的后 \(j\) 位可以达到的最小值,\(r_x\)\(b_x\) 的后 \(j\) 位可以达到的最大值,那么有 \(\prod\limits_{t\ne i}(r_t-l_t+1)\) 种合法的序列 \(\{b\}\)

为什么?因为由于 \(b_i\) 即没有达到上界,也没有达到下界,因此 \(b_i\) 的后 \(j\) 位可以取遍 \([0,2^j)\) 中所有的值,因此对于任意一种其他位置后 \(j\) 位的填法都唯一存在一个 \(b_i\) 的后 \(j\) 位的填法,满足它们的异或和等于 \(S\)

知道了这个性质之后,我们考虑枚举第一位存在某个 \(b_i\) 出现第三种情况的位置:第 \(j\) 位。那么对于第 \(j+1\) 位到第 \(m-1\) 位而言,每个 \(b_i\) 只有达到上界和达到下界两种可能,我们考虑再 \(2^n\) 枚举这个状态 \(st\)\(st\) 的第 \(i-1\) 位为 \(0\) 表示 \(b_i\) 的第 \(j+1\)\(m-1\) 位达到下界,第 \(i-1\) 位为 \(1\) 表示 \(b_i\) 的第 \(j+1\)\(m-1\) 位达到上界。

枚举这两个值之后,擅长解决计数问题的 \(dp\) 就要派上用场了,我们考虑记 \(dp_{i,0/1/2/3}\) 表示当前决策了前 \(i\) 个数的第 \(j\) 位的值,后面一维的定义如下:

  • \(0\) 表示当前没有出现第三种情况,且该位异或和为 \(0\)
  • \(1\) 表示当前没有出现第三种情况,且该位异或和为 \(1\)
  • \(2\) 表示当前出现了第三种情况,且该位异或和为 \(0\)
  • \(3\) 表示当前出现了第三种情况,且该位异或和为 \(1\)

转移就分这一位填 \(0\) 和这一位填 \(1\) 两种情况处理即可,具体来说:

  • 如果 \(a_{i+1}\)\(a_i\) 的第 \(j\) 位到第 \(m-1\) 位都相同,那么第 \(j\) 位只能填与 \(a_i\) 的第 \(j\) 位相同的值,方案数为 \(a_{i+1}-a_i+1\)
  • 如果 \(a_{i+1}\)\(a_i\) 的第 \(j+1\) 位到第 \(m-1\) 位都相同,但第 \(j\) 位不同,那么记 \(X=\lfloor\dfrac{a_{i+1}}{2^j}\rfloor\times 2^j\)\(b_i\)\(j\) 位填 \(0\)\(X-a_i\) 种可能,填 \(1\)\(a_{i+1}-X+1\) 种可能,并且都不会出现情况 \(3\),随便转移一下即可。
  • 否则如果 \(st\) 的第 \(i-1\) 位为 \(0\),则 \(b_i\) 的第 \(j+1\)\(m-1\) 位与 \(a_i\) 相同,继续分情况讨论:
    • 如果 \(a_i\) 的第 \(j\) 位为 \(1\),那么 \(b_i\) 的第 \(j\) 位只能填 \(1\),方案数为 \(\lfloor\dfrac{a_{i}}{2^j}\rfloor\times 2^j+2^j-a_i\),并且不会出现情况 \(3\)
    • 如果 \(a_i\) 的第 \(j\) 位为 \(0\),那么 \(b_i\) 的第 \(j\) 位可 \(0\)\(1\),填 \(0\) 的方案数依旧是 \(\lfloor\dfrac{a_{i}}{2^j}\rfloor\times 2^j+2^j-a_i\),填 \(1\) 的方案数则是 \(2^j\),并且会出现情况 \(3\)
  • 如果 \(st\) 的第 \(i-1\) 位为 \(1\),与 \(st\) 的第 \(i-1\) 位为 \(0\) 的情况类似,镜像一下即可。

最终累加入答案的值即为 \(dp_{n-1,2+d}\),其中 \(d\)\(S\)\(j\) 位的值。

时间复杂度 \(2^n·nm\)

const int MAXN=17;
const int MOD=998244353;
int n,m,ans=0,dif[MAXN+5];ll l[MAXN+5],r[MAXN+5],sum=0;
bool check(int st,int k){
	ll ss=0;
	for(int i=1;i<n;i++){
		if(st>>(i-1)&1){
			if(dif[i]<k) return 0;
			ss^=r[i];
		} else ss^=l[i];
	} return (ss>>k)==(sum>>k);
}
int dp[MAXN+3][4];
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%lld",&l[i]),r[i-1]=l[i],sum^=l[i];
	for(int i=1;i<n;i++){
		dif[i]=-1;
		for(int j=m-1;~j;j--) if((l[i]>>j&1)^(r[i]>>j&1)){
			dif[i]=j;break;
		}
	}
	for(int i=0;i<(1<<n-1);i++) if(check(i,0)) ans++;
	for(int i=0;i<m;i++) for(int j=0;j<(1<<n-1);j++) if(check(j,i+1)){
		memset(dp,0,sizeof(dp));dp[0][0]=1;
		for(int p=1;p<n;p++){
			if(dif[p]<i){
				int d=l[p]>>i&1,num=(r[p]-l[p]+1)%MOD;
				for(int t=0;t<4;t++) dp[p][t]=(dp[p][t]+1ll*dp[p-1][t^d]*num)%MOD;
			} else if(dif[p]==i){
				ll lim=l[p]|((1ll<<i)-1);int num0=(lim-l[p]+1)%MOD,num1=(r[p]-lim)%MOD;
				for(int t=0;t<4;t++){
					dp[p][t]=(dp[p][t]+1ll*dp[p-1][t]*num0)%MOD;
					dp[p][t]=(dp[p][t]+1ll*dp[p-1][t^1]*num1)%MOD;
				}
			} else if(j>>(p-1)&1){
				if(r[p]>>i&1){
					ll lim=r[p]>>i<<i;
					int num0=(1ll<<i)%MOD,num1=(r[p]-lim+1)%MOD;
					for(int t=0;t<4;t++) dp[p][t]=(dp[p][t]+1ll*dp[p-1][t^1]*num1)%MOD;
					for(int t=2;t<4;t++) dp[p][t]=(0ll+dp[p][t]+1ll*num0*dp[p-1][t]+dp[p-1][t&1])%MOD;
				} else {
					ll lim=r[p]>>i<<i;
					int num0=(r[p]-lim+1)%MOD;
					for(int t=0;t<4;t++) dp[p][t]=(dp[p][t]+1ll*dp[p-1][t]*num0)%MOD;
				}
			} else {
				if(~l[p]>>i&1){
					ll lim=l[p]|((1ll<<i)-1);
					int num0=(lim-l[p]+1)%MOD,num1=(1ll<<i)%MOD;
					for(int t=0;t<4;t++) dp[p][t]=(dp[p][t]+1ll*dp[p-1][t]*num0)%MOD;
					for(int t=2;t<4;t++) dp[p][t]=(0ll+dp[p][t]+1ll*num1*dp[p-1][t^1]+dp[p-1][(t^1)&1])%MOD;
				} else {
					ll lim=l[p]|((1ll<<i)-1);
					int num1=(lim-l[p]+1)%MOD;
					for(int t=0;t<4;t++) dp[p][t]=(dp[p][t]+1ll*dp[p-1][t^1]*num1)%MOD;
				}
			}
		}
		int d=sum>>i&1;
		ans=(ans+dp[n-1][2|d])%MOD;
	} printf("%d\n",ans);
	return 0;
}
posted @ 2021-07-11 16:25  tzc_wk  阅读(84)  评论(0)    收藏  举报