题解:P8337 [Ynoi2004] rsxc
首先这道题没有修改,应该要预处理一些东西。
对于合法的区间,假如它所有数塞进线性基里共 \(k\) 个数,那么线性基中任意几个数(包括0个)异或和都被这个区间包含,则这个区间共 \(2^k\) 种数。
于是我们得到一个性质,合法区间线性基大小为 \(k\) 且颜色数为 \(2^k\)。
我们肯定要先枚举 \(k\),再预处理合法区间。
又有一个性质,\(k\) 相同的情况下,对于每个 \(i\),合法区间的左端点肯定组成区间 \([l1,l2]\),对于每个 \(j \in [l1,l2]\),区间 \([j,i]\) 是合法区间,以下称这种 \([l1,l2]\) 为贡献区间,称 \(i\) 为贡献区间对应点。

对于每个 \(k\),分成两个 Part,我们先预处理出线性基大小为 \(k\) 的区间,再预处理出颜色数为 \(2^k\) 的区间,最后取交集求出每贡献区间对应点为 \(i\) 的贡献区间 \(resl_{k,i},resr_{k,i}\)(\(l1,l2\))。
Part 1
线性基大小为 \(k\)。
我首先想到线段树套线性基,但空间时间都炸了。
有一个神奇的黑科技,前缀线性基(CF1100F),就是每次插入线性基的数占最高位,能快速求出区间 \([l,r]\) 的线性基大小。
插入每一个 \(a_i\) 后,我们把线性基的 \(pos\) 数组(时间戳)排序,线性基大小为 \(k\) 的区间是 \([[sortedpos_{k}+1,sortedpos_{k-1}],i]\),即对于每个 \(j \in [sortedpos_{k}+1,sortedpos_{k-1}]\),区间 \([j,i]\) 是合法区间。
如果每次查询都重新排序,那太慢了,我们采取每插入一个数重排一个数的策略。
namespace Linear_Basis{
int lb[32],pos[32],sorted_pos[32];
inline void modify_rank(int x,int y){
for(int i=0;i<=30;i++)
{
if(sorted_pos[i]==x){//找到权值为x的sorted_pos
//由于每次换的y肯定是最大的,所以直接把y排在最大的一位
for(int j=i;j>0;j--)
{
sorted_pos[j]=sorted_pos[j-1];
}
sorted_pos[0]=y;
break;
}
}
}
inline void insert(int p,int val){//插入第p个数,权值为val
if(!val){
return ;
}
int lstp=p;//记录初始的p
for(int i=30;i>=0;i--)
{
if(val&(1<<i)){
if(!lb[i]){
lb[i]=val;
pos[i]=p;
p=0;
break;
}
else if(pos[i]<p){
swap(pos[i],p);
swap(lb[i],val);
}
val^=lb[i];
}
}
if(p!=lstp){//将sorted_pos数组中的p改为lstp
modify_rank(p,lstp);
}
}
inline void update(int p){
for(int i=0;i<=30;i++)
{
if(!sorted_pos[i]){
break;
}
resr[i+1][p]=sorted_pos[i];
resl[i+1][p]=sorted_pos[i+1]+1;
}
}
}
using Linear_Basis::insert;
using Linear_Basis::update;
Part 2
颜色数为 \(2^k\)。
这东西显然左右端点单调,可以双指针。
先预处理出每个点的权值上一次出现的位置 \(lst\),下一次出现的位置 \(nxt\)。
//离散化
for(int i=1;i<=n;i++)
{
b[i]=a[i];
uni[i]=a[i];
}
sort(uni+1,uni+1+n);
int tot=unique(uni+1,uni+1+n)-uni-1;
for(int i=1;i<=n;i++)
{
b[i]=lower_bound(uni+1,uni+1+tot,a[i])-uni;
}
//预处理lst,nxt
for(int i=1;i<=n;i++)
{
lst[i]=0;
nxt[i]=n+1;
}
for(int i=1;i<=n;i++)
{
lst[i]=lstnum[b[i]];
nxt[lstnum[b[i]]]=i;
lstnum[b[i]]=i;
}

(红色为 \(lst\),蓝色为 \(nxt\))
维护两个指针 \(l1,l2\) ,加入 \(a_i\) 时,分类讨论。
-
\(lst_i \ge l2\),\(l2\) 跳到下一个权值非 \(a_{l2}\) 的位置。
-
\(lst_i < l1\),\(l1\) 跳到第一个 \(nxt_{l1}>j\) 的位置,\(l2\) 跳到第一个 \(nxt_{l2}>j\) 的位置。
再把 \(l2\) 尽量往后跳(\(nxt_{l2} \le j\))。
线性基大小为 \(k\) 的区间是 \([[l1,l2],i]\),即对于每个 \(j \in [l1,l2]\),区间 \([j,i]\) 是合法区间。
for(int i=1,colnum=2;colnum<=n;i++,colnum<<=1)//枚举k(颜色数colnum为2^k)
{
int l1=1,l2=1,cnt=1;
for(int j=2;j<=n;j++)
{
if(lst[j]>=l2){
while(a[l2]==a[j]&&l2<j)
{
l2++;
}
}
else if(lst[j]<l1){
cnt++;
if(cnt==colnum+1){
while(nxt[l1]<=j)
{
l1++;
}
l1++;
while(nxt[l2]<=j)
{
l2++;
}
l2++;
cnt--;
}
}
while(nxt[l2]<=j&&l2<j)
{
l2++;
}
if(cnt!=colnum){
continue;
}
/*统计答案*/
}
}
现在我们来看询问。
每个 \(k,i\) 预处理出 \(lstid_{k,i},nxtid_{k,i}\),表示第一个和最后一个包含 \(i\) 的贡献区间。
询问 \(\operatorname{query}(l,r)\),等价于求,每个 \(k\) 的第 \(L=lstid_{k,l}\) 个至第 \(R=nxtid_{k,r}\) 个贡献区间(\(res_{k,1} \sim res_{k,r}\))与 \([l,r]\) 的交的长度和。

注意到,\(resl_k,resr_k\) 是单调的,可以把蓝色区间分为三部分,左边散块,中间整块,右边散块。

-
\(\text{左边散块贡献} = \text{左边散块区间右端点和} - l \times \text{左边散块区间数量}\)
-
\(\text{中间整块贡献} = \text{中间整块区间长度和} = \text{中间整块区间右端点和} - \text{中间整块区间左端点和}\)
-
\(\text{右边散块贡献} = r \times \text{右边散块区间数量} - \text{右边散块区间左端点和}\)
所以我们预处理出贡献区间的左端点前缀和 \(suml\),右端点前缀和 \(sumr\),第一个左端点不少于 \(i\) 的贡献区间编号 \(maxl_i\),最后一个右端点不超过 \(i\) 的贡献区间编号 \(maxr_i\)。
注意判前后的 0。
for(int i=0;i<=M;i++)//枚举k
{
for(int j=1;j<=n;j++)//枚举i
{
if(!vis[i][j]){//vis表示是否有贡献区间
resl[i][j]=0;
resr[i][j]=0;
if((!i)&&(!a[j])){
vis[i][j]=true;
resl[i][j]=pre0[j];
resr[i][j]=j;
}
}
if(vis[i][j]){
cnt[i]++;
resl[i][cnt[i]]=resl[i][j];
resr[i][cnt[i]]=resr[i][j];
lstid[i][j]=cnt[i];
if(!nxtid[i][resr[i][cnt[i]]]){
nxtid[i][resr[i][cnt[i]]]=cnt[i];
}
if(!maxl[i][resl[i][j]]){
maxl[i][resl[i][j]]=cnt[i];
}
maxr[i][resr[i][j]]=cnt[i];
}
else{
lstid[i][j]=lstid[i][j-1];
}
}
maxr[i][0]=0;
maxl[i][n+1]=cnt[i]+1;
for(int j=1;j<=n;j++)
{
if(!maxr[i][j]){
maxr[i][j]=maxr[i][j-1];
}
}
for(int j=n;j>=1;j--)
{
if(!maxl[i][j]){
maxl[i][j]=maxl[i][j+1];
}
}
nxtid[i][n+1]=cnt[i]+1;
for(int j=n;j>=1;j--)
{
if(!nxtid[i][j]){
nxtid[i][j]=nxtid[i][j+1];
}
}
//suml,sumr开指针省空间
suml[i]=new long long[cnt[i]+1];
sumr[i]=new long long[cnt[i]+1];
for(int j=1;j<=cnt[i];j++)
{
suml[i][j]=suml[i][j-1]+resl[i][j];
sumr[i][j]=sumr[i][j-1]+resr[i][j];
}
}
long long solve(int ql,int qr){
long long res=0;
for(int i=0;i<=M;i++)
{
if(!cnt[i]){
continue;
}
int L=nxtid[i][ql],R=lstid[i][qr];
if(L>R){
continue;
}
int ml=maxl[i][ql],mr=maxr[i][qr];
//判断边界
ml=min(ml,R+1);
mr=max(mr,L+1);
ml=max(ml,L);
mr=min(mr,R);
if(L<=ml-1){//左边散块贡献
int len=(ml-1)-L+1;
res+=(sumr[i][ml-1]-sumr[i][L-1])-1ll*len*ql+len;
}
if(mr+1<=R){//中间整块贡献
int len=R-(mr+1)+1;
res+=1ll*len*qr-(suml[i][R]-suml[i][mr+1])+len;
}
if(ml<=mr){//右边散块贡献
int len=mr-ml+1;
res+=(sumr[i][mr]-sumr[i][ml-1])-(suml[i][mr]-suml[i][ml-1])+len;
}
}
return res;
}
然后你就可以去卡 P8283 了。

浙公网安备 33010602011771号