[ CQOI 2018 ] 异或序列

探讨了在一个长为n的数列中,如何高效计算指定区间内异或和等于特定值k的非空子段数量。通过前缀异或和的概念,将问题转化为寻找特定异或对的数量,并提出了一种动态维护计数的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

\(\\\)

Description


给出一个长为 \(n\) 的数列 \(A\)\(k\),多次询问:

对于一个区间 \([L_i,R_i]\),问区间内有多少个不为空的子段异或和为 \(k\)

  • \(n,m,k,A_i\le 10^5\)

\(\\\)

Solution


注意到一件有趣的事,就是每次询问的 \(k\) 相同。

因为 \(a\oplus a=0\),所以子段异或问题可以看作前缀异或和的异或,即
\[ a[i]\oplus a[i+1]\oplus...\oplus a[j]=sum[i-1]\oplus sum[j] \]
其中 \(sum[i]=a[1]\oplus a[2]\oplus...\oplus a[i]\)

那么问题转化为,存在对少对 \(i,j\in[L_i-1,R_i],i!=j\) ,满足
\[ sum[i]\oplus sum[j]=k \]
注意区间问题,因为区间做差的原理是减掉 \(l-1\)

然后可以注意到,一个值 \(x\) 若想要构成 \(k\) ,其对应的另一个值是固定的。

也就是说,我们的组合方案是确定的。

当新加入一个可选值 \(x\) ,我们的方案数就会 \(+cnt[x^k]\) ,其中 \(cnt[i]\) 表示当前含有可选值 \(i\) 的个数。

可以证明,这种计数方式不会算重,因为每个数字加入时只会计算当前已经有对应的值。

当去掉一个值的时候,方案数 \(-cnt[x^k]\) 即可。

\(\\\)

还有一个问题,就是关于 \(k=0\) 的情况。

此时每个值显然不能计算上自己和自己异或的贡献。

删除时当然也要注意不能多减掉自己异或自己的情况。

只需在 adddel 的时候交换一下操作顺序即可,具体看代码。

\(\\\)

Code


突然失智......Debug 2h 竟只是因为 \(l\) 没有减 \(1\) ......

还要注意,刚开始 \(0\) 号位置也有一个贡献。

#include<cmath>
#include<cstdio>
#include<cctype>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 100010
#define R register
#define gc getchar
using namespace std;
typedef long long ll;

inline ll rd(){
  ll x=0; bool f=0; char c=gc();
  while(!isdigit(c)){if(c=='-')f=1;c=gc();}
  while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
  return f?-x:x;
}

ll n,m,k,ans,bl[N],cnt[1<<18],s[N],res[N];

struct Q{ll l,r,id;}q[N];

inline bool cmp1(Q x,Q y){
  return bl[x.l]==bl[y.l]?x.r<y.r:bl[x.l]<bl[y.l];
}

inline bool cmp2(Q x,Q y){return x.id<y.id;}

inline void del(int p){
  --cnt[s[p]];
  ans-=cnt[k^s[p]];
}

inline void add(int p){
  ans+=cnt[k^s[p]];
  ++cnt[s[p]];
}

int main(){
  n=rd(); m=rd(); k=rd();
  ll t=sqrt(n);
  for(R ll i=1;i<=n;++i){
    s[i]=s[i-1]^rd();
    bl[i]=i/t+1;
  }
  for(R ll i=1;i<=m;++i){
    q[i].l=rd()-1; q[i].r=rd(); q[i].id=i;
  }
  sort(q+1,q+1+m,cmp1);
  ll nowl=0,nowr=0;
  cnt[0]=1;
  for(R ll i=1;i<=m;++i){
    while(nowl>q[i].l){--nowl;add(nowl);}
    while(nowl<q[i].l){del(nowl);++nowl;}
    while(nowr<q[i].r){++nowr;add(nowr);}
    while(nowr>q[i].r){del(nowr);--nowr;}
    res[q[i].id]=ans;
  }
  for(R ll i=1;i<=m;++i) printf("%lld\n",res[i]);
  return 0;
}

转载于:https://www.cnblogs.com/SGCollin/p/9994231.html

# P4462 [CQOI2018] 异或序列 ## 题目描述 已知一个长度为 $n$ 的整数数列 $a_1,a_2,\dots,a_n$,给定查询参数 $l,r$,问在 $a_l,a_{l+1},\dots,a_r$ 区间内,有多少子区间满足异或和等于 $k$。也就是说,对于所有的 $x,y (l \leq x \leq y \leq r)$,能够满足 $a_x \oplus a_{x+1} \oplus \dots \oplus a_y = k$ 的 $x,y$ 有多少组。 ## 输入格式 输入文件第一行,为 $3$ 个整数 $n,m,k$。 第二行为空格分开的 $n$ 个整数,即 $a_1,a_2,..a_n$。 接下来 $m$ 行,每行两个整数 $l_j,r_j$,表示一次查询。 ## 输出格式 输出文件共 $m$ 行,对应每个查询的计算结果。 ## 输入输出样例 #1 ### 输入 #1 ``` 4 5 1 1 2 3 1 1 4 1 3 2 3 2 4 4 4 ``` ### 输出 #1 ``` 4 2 1 2 1 ``` ## 说明/提示 对于 $30\%$ 的数据,$1 \leq n, m \leq 1000$。 对于 $100\%$ 的数据,$1 \leq n, m \leq 10^5$,$0 \leq k, a_i \leq 10^5$,$1 \leq l_j \leq r_j \leq n$。 #include <bits/stdc++.h> #define ll long long using namespace std; const ll N=2e5; struct node{ ll l,r,f,ans; }b[N+5]; ll n,m,k; ll blk[N+5],lenb; ll a[N+5],pre[N+5]; ll cnt[N+5]; ll lp,rp,s; bool cmp(node l1,node l2){ if(blk[l1.l]==blk[l2.l]) return l1.r<l2.r; return blk[l1.l]<blk[l2.l]; } bool cmpf(node l1,node l2){ return l1.f<l2.f; } void solve(ll l,ll r,ll &ans){ while(lp<l){ s-=cnt[k^pre[lp-1]]; cnt[pre[lp]]--; lp++; } while(lp>l){ lp--; cnt[pre[lp]]++; s+=cnt[k^pre[lp-1]]; } while(rp<r){ rp++; s+=cnt[k^pre[rp]]; cnt[pre[rp]]++; if((pre[lp-1]^pre[rp])==k) s++; } while(rp>r){ s-=cnt[k^pre[rp]]; cnt[pre[rp]]--; if((pre[lp-1]^pre[rp])==k) s--; rp--; } ans=s; return; } int main(){ scanf("%lld%lld%lld",&n,&m,&k); ll sx=1; lenb=(ll)sqrt(n); for(ll i=1;i<=n;i++){ blk[i]=sx; if(sx%lenb==0) sx++; } for(ll i=1;i<=n;i++){ scanf("%lld",&a[i]); pre[i]=(pre[i-1]^a[i]); } for(ll i=1;i<=m;i++){ scanf("%lld%lld",&b[i].l,&b[i].r); } sort(b+1,b+m+1,cmp); lp=rp=cnt[pre[1]]=1; if(k==0) s=1; for(ll i=1;i<=m;i++){ solve(b[i].l,b[i].r,b[i].ans); } sort(b+1,b+m+1,cmpf); for(ll i=1;i<=m;i++) printf("%lld\n",b[i].ans); return 0; } 不改变原来代码写法情况下标出并修正错误
最新发布
07-31
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值