CF1456E XOR-ranges 题解

CF1456E XOR-ranges

思路

写完发现可能确实没到非常难的地步,但思路确实不好想,在想明白之前理解了好久。这道题一定要理清楚为什么才开始写,否则写到一半不知道自己在干什么了。

当然先去考虑拆位去计算贡献。但是发现上界和下界的限制极其难以处理。注意到普通数位 DP 处理上下界的套路就是看是否已经填了的数的二进制前缀与上界或下界的二进制前缀相同。如果相同,我称之为卡位

然后破局的关键就是发现一个关键性质:由于贡献是通过异或来求的,因此如果对于一段数,所有的当前位都可以随便取,也就是这段数中当前要填的所有位都没有卡位,那么也就是说中间的所有数的当前位都可以随便填。那么中间贡献的最小值就一定是 0,因为随便填一定可以构造出一种方案使得任意相邻的这一位异或起来是 0 使得没有贡献。(其实就是这一段的这位数全部填 0)

于是我们惊奇的发现对于这一段数 \([l,r]\) 而言,其这一位的贡献一定是 0,于是其贡献实际上等价于 \(l-1\)\(r+1\) 在这一位的贡献。

于是其类似与一个区间 DP 的形式。我们直接设 \(f_{i,l,r,x=0/1,y=0/1,L=0/1,R=0/1}\) 表示考虑区间 \([l,r]\) 的第 \([i,k-1]\) 位填完的最小贡献。其限制是对于区间 \(l,r\),其从低向高第 \(i\) 位都可以随便填,但是 \(l-1\)\(r+1\) 不行,钦定其 \([i+1,k-1]\) 位与 \(lim_{l-1,x}\)\(lim_{r+1,y}\) 对应相同。同时 \(l-1\)\(i\) 这一位的取值为 \(lim_{l-1,x}\) 在二进制下的第 \(i\) 位异或上 \(L\)\(r+1\) 这一位的取值同理。其中 \(lim_{k,0}\) 表示第 \(k\) 位的值的下界,上界同理。

乍一看这个定义很奇怪且复杂。实际上这是为了方便转移。下面将状态对应的 \(f\) 简写为 \(w\)
我们将转移分为两种情况:

  1. 区间范围不变,直接将第 \(i\) 位填了。那么就有 \(w=f_{i+1,l,r,x,y,0,0}+\) \(l-1\)\(r+1\) 当前位的贡献。由于我们通过 \(i,x,y,L,R\) 能够直接知道这两个位置当前位的具体数值,因此我们直接算即可。而那个 \(f\)\(L,R\)\(0\) 的原因是由于要求了 \(l-1\)\(r+1\) 上的数值除了第 \(i\) 位,比其高的位都卡位,因此其取值就是 \(lim_{l-1/r+1,x/y}\) 本身。
  2. 从区间合并转移过来。如果要区间合并,那么就是枚举一个位置 \(loc\),然后从 \(f_{i,l,loc-1}\)\(f_{i,loc+1,r}\) 转移而来。为了避免大量的重复转移,我们钦定 \(loc\) 的第 \(i\) 位一定不卡位,否则就会产生巨量的重复情况导致复杂度假掉。于是我们枚举其不卡哪一个位。但由于我们又钦定了边界外的那个点在 \(i\) 以上的位一定卡位,因此其取值只能是其卡的边界的高位的第 \(i\) 位异或上 \(1\) 的取值。于是其对应方向的 \(L/R\) 一定是 \(1\)

(其实更推荐在弄明白定义后直接看代码)

code

唯一的问题是这样转移无法覆盖到取值恰好是上界或下界的情况,于是需要特判。逻辑与上面类似。
(这辈子写过的最详细的注释)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=55,inf=1e18+7;
int n,K,c[N],lim[N][2],f[N][N][N][2][2][2][2];
void upd(int &x,int y){x=min(x,y);}
int get(int i,int l,int r,int x,int y,int L,int R){
	l-=1,r+=1;if(l<1||r>n||l>r)return 0;//注意判断错误需要提前将 l-1 和 r+1,而不是只在计算的时候如此 
	return (((lim[l][x]>>i)&1)^L^((lim[r][y]>>i)&1)^R)*c[i];//从这里可以看出这里 L 和 R 用一种很简洁的方式表示出了两端外的精确取值 
}
bool cmp(int i,int loc,int t){//通过 L 和 R 避免了过多的特判。实际上只要求了精确位不压,同时保证了这一位及其低位可以随便取。(但同时保证了所有的更高位都是压的) 
	return ((((lim[loc][t]>>i)^1)<<i)>=lim[loc][0]&&(((lim[loc][t]>>i)^1)<<i)+((1ll<<i)-1)<=lim[loc][1]);
}
int solve(int i,int l,int r,int x,int y,int L,int R){//这里困难的是理解 L 和 R 的意义。实际上只是与 x 与 y 一同确定两端外的值 
	int &w=f[i][l][r][x][y][L][R];
	if(w<inf)return w;w=inf-1;if(i==K)return w=(l>r?0:w);
	upd(w,solve(i+1,l,r,x,y,0,0)+get(i,l,r,x,y,L,R));//为 0 的意义是要求完全压位。因为两端外的值的高位一定都是压的 
	for(int loc=l;loc<=r;loc++){for(int t=0;t<2;t++){if(cmp(i,loc,t))upd(w,solve(i,l,loc-1,x,t,L,1)+solve(i,loc+1,r,t,y,1,R));}}
	if(!i){//完全压位要记得特判,几乎等价于其他写法中外面套的那个 DP。 
		for(int loc=l;loc<=r;loc++){
			for(int t=0;t<2;t++)upd(w,solve(i,l,loc-1,x,t,L,0)+solve(i,loc+1,r,t,y,0,R));
		}
	}
	return w;
}
signed main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>K;for(int i=1;i<=n;i++)cin>>lim[i][0]>>lim[i][1];
	for(int i=0;i<K;i++)cin>>c[i];
	memset(f,0x3f3f,sizeof(f));
	cout<<solve(0,1,n,0,0,0,0);return 0;
}
posted @ 2025-07-30 21:16  all_for_god  阅读(12)  评论(0)    收藏  举报