洛谷题单 算法2-3 字符串
洛谷题单 算法2-3 字符串
KMP模板
使用前保证字符串下标从 \(1\) 开始。e.g. s="?"+s
;
template<class Type>
struct KMP{
vector<int> init(Type s){
int n=(int)s.size()-1;
vector<int> nxt(n+1,0);
for(int i=2;i<=n;i++){
nxt[i]=nxt[i-1];
while(nxt[i]&&s[nxt[i]+1]!=s[i]) nxt[i]=nxt[nxt[i]];
nxt[i]+=(s[nxt[i]+1]==s[i]);
}
return nxt;
}
vector<int> match(Type s,Type t){
vector<int> pos;
vector<int> nxt=init(t);
int n=(int)s.size()-1,m=(int)t.size()-1;
for(int i=1,j=0;i<=n;i++){
while(j&&s[i]!=t[j+1]) j=nxt[j];
j+=(t[j+1]==s[i]);
if(j==m){//匹配成功
j=nxt[j];
pos.push_back(i-m+1);
}
}
return pos;
}
};
KMP<string> kmp;
CF25E - Test KMP 最短母串问题
这是 CF25E 的数据加强版。
考虑贪心,想要让字符串 \(s_1,s_2,s_3\) 都作为子串出现的字符串最短,就要让 \(s_1\) 的后缀尽量和 \(s_2\) 的前缀匹配。
然后拼接 \(s_1\) 和 \(s_2\) 不匹配的后半部分组成新的字符串,继续与 \(s_3\) 进行这个操作即可。
那么看到 前后缀匹配,自然想到 KMP 算法。
想要求出 \(s_1\) 和 \(s_2\) 的最长前后缀匹配,可以转化成求 s2+"?"+s1
的 \(Border\) 。
后续同理。这里要注意如果 \(s2\) 已经是 \(s_1\) 的子串,那么就不用进行这些操作。 \(s_3\) 同理。
判断是否为子串直接 KMP 进行匹配即可。
最后我们排列一下字符串的顺序,一共只有 \(6\) 种情况,维护答案即可。
#include<bits/stdc++.h>
using namespace std;
using i64=long long;
template<class Type>
struct KMP{
vector<int> init(Type s){
int n=(int)s.size()-1;
vector<int> nxt(n+1,0);
for(int i=2;i<=n;i++){
nxt[i]=nxt[i-1];
while(nxt[i]&&s[nxt[i]+1]!=s[i]) nxt[i]=nxt[nxt[i]];
nxt[i]+=(s[nxt[i]+1]==s[i]);
}
return nxt;
}
vector<int> match(Type s,Type t){
vector<int> pos;
vector<int> nxt=init(t);
int n=(int)s.size()-1,m=(int)t.size()-1;
for(int i=1,j=0;i<=n;i++){
while(j&&s[i]!=t[j+1]) j=nxt[j];
j+=(t[j+1]==s[i]);
if(j==m){//匹配成功
j=nxt[j];
pos.push_back(i-m+1);
}
}
return pos;
}
};
KMP<string> kmp;
void Showball(vector<string> s){
//计算s后缀和t前缀的最大匹配长度
auto get=[&](string s,string t){
string tmp=t+s;
return kmp.init(tmp).back();
};
auto calc=[&](string s0,string s1,string s2){
string _s="?"+s0,_s1="?"+s1,_s2="?"+s2;
if(kmp.match(_s,_s1).empty()){
int t=get(_s,_s1);
_s+=s1.substr(t);
}
if(kmp.match(_s,_s2).empty()){
int t=get(_s,_s2);
_s+=s2.substr(t);
}
return (int)_s.size()-1;
};
vector<int> p(3);
iota(p.begin(),p.end(),0);
int ans=4e5;
do{
ans=min(ans,calc(s[p[0]],s[p[1]],s[p[2]]));
}while(next_permutation(p.begin(),p.end()));
cout<<ans<<"\n";
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t=1;
//cin>>t;
while(t--){
vector<string> s(3);
while(cin>>s[0]>>s[1]>>s[2])
Showball(s);
}
return 0;
}
最短母串问题,所以我们可以把代码写的通用一些。
//最短母串问题
void Showball(){
int n;
cin>>n;
vector<string> s(n);
for(int i=0;i<n;i++){
cin>>s[i];
s[i]="?"+s[i];
}
auto get=[&](string s,string t){
string tmp=t+s;
return kmp.init(tmp).back();
};
auto calc=[&](vector<string> str){
string s=str[0];
int n=str.size();
for(int i=1;i<n;i++){
if(kmp.match(s,str[i]).empty()){
int t=get(s,str[i]);
s+=str[i].substr(t+1);
}
}
return (int)s.size()-1;
};
int ans=2e9;
sort(s.begin(),s.end());
do{
ans=min(ans,calc(s));
}while(next_permutation(s.begin(),s.end()));
cout<<ans<<"\n";
}
[ABC343G] Compress Strings 状压+KMP
上一题的加强版, \(N\) 从 \(3\) 扩大到了 \(20\)。
不能再暴力枚举顺序,考虑状压 \(DP\) 。
令 \(dp_{i,j}\) 表示状态为 \(i\) ,且结尾是 \(j\) 字符串时的最小长度。
状态转移:\(dp_{i,j}=min_{j\ne k}\{ dp_{i-2^j,k}-g_{k,j}+|S_j|\}\)。
其中 \(g_{k,j}\) 表示 \(s_k\) 的后缀与 \(s_j\) 前缀的最长匹配长度。用 kmp 维护即可。
我们先去重,并且将已经是其他字符串的子串也去除。再进行 \(DP\)。
#include<bits/stdc++.h>
using namespace std;
using i64=long long;
const int N = 22;
template<class Type>
struct KMP{
vector<int> init(Type s){
int n=(int)s.size()-1;
vector<int> nxt(n+1,0);
for(int i=2;i<=n;i++){
nxt[i]=nxt[i-1];
while(nxt[i]&&s[nxt[i]+1]!=s[i]) nxt[i]=nxt[nxt[i]];
nxt[i]+=(s[nxt[i]+1]==s[i]);
}
return nxt;
}
vector<int> match(Type s,Type t){
vector<int> pos;
vector<int> nxt=init(t);
int n=(int)s.size()-1,m=(int)t.size()-1;
for(int i=1,j=0;i<=n;i++){
while(j&&s[i]!=t[j+1]) j=nxt[j];
j+=(t[j+1]==s[i]);
if(j==m){//匹配成功
j=nxt[j];
pos.push_back(i-m+1);
}
}
return pos;
}
};
KMP<string> kmp;
int dp[1<<N][N];
void Showball(){
int n;
cin>>n;
vector<string> s(n);
for(int i=0;i<n;i++){
cin>>s[i];
}
sort(s.begin(),s.end());
s.erase(unique(s.begin(),s.end()),s.end());
n=s.size();
vector<string> cand;
for(int i=0;i<n;i++){
bool ok=true;
for(int j=0;j<n;j++){
if(i!=j&&s[j].find(s[i])!=s[i].npos){
ok=false;
break;
}
}
if(ok) cand.push_back("?"+s[i]);
}
auto get=[&](string s,string t){
string tmp=t+s;
return kmp.init(tmp).back();
};
n=cand.size();
vector<vector<int>> g(n,vector<int>(n));
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
if(i!=j) g[i][j]=get(cand[i],cand[j]);
}
}
memset(dp,0x3f,sizeof dp);
for(int i=0;i<n;i++) dp[1<<i][i]=(int)cand[i].size()-1;
for(int i=0;i<(1<<n);i++){
for(int j=0;j<n;j++){
if(i>>j&1){
for(int k=0;k<n;k++){
if(j!=k&&i>>k&1){
dp[i][j]=min(dp[i][j],dp[i-(1<<j)][k]-g[k][j]+(int)cand[j].size()-1);
}
}
}
}
}
int ans=2e9;
for(int i=0;i<n;i++){
ans=min(ans,dp[(1<<n)-1][i]);
}
cout<<ans<<"\n";
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t=1;
//cin>>t;
while(t--){
Showball();
}
return 0;
}
Trie 字典树
P2922 [USACO08DEC] Secret Message G Trie字典树
计算相同前缀数量,考虑使用字典树,直接把读入的 \(2\) 进制数当成字符串处理即可。把截获的信息全部加入字典树,然后查询即可。
字典树记录一下经过每个节点的单词数 \(cnt\) ,以及以该节点为终点的单词数 \(ed\) 。
那么考虑查询:共有两种情况,一种是走完了该单词,一种是没走完。
查询的时候累加所有的 \(cnt\) 。如果没走完这就是答案。
如果走完了,说明存在比当前串还长的拥有公共前缀的串,那我们就要加上经过这个单词结尾点的 \(cnt\) ,注意还需要减去结尾点的 \(ed\)。因为会有重复。
#include<bits/stdc++.h>
using namespace std;
using i64=long long;
const int N = 5e5+10;
int ch[N][26],cnt[N],ed[N],idx;
void insert(string s){
int p=0;
for(auto c:s){
int j=c-'0';
if(!ch[p][j]) ch[p][j]=++idx;
p=ch[p][j];
cnt[p]++;
}
ed[p]++;
}
int query(string s){
int p=0,ret=0;
for(auto c:s){
int j=c-'0';
if(!ch[p][j]) return ret;
p=ch[p][j];
ret+=ed[p];
}
return ret-ed[p]+cnt[p];
}
void Showball(){
int m,n;
cin>>m>>n;
for(int i=0;i<m;i++){
int k;
cin>>k;
string s="";
for(int j=0;j<k;j++){
char c;
cin>>c;
s+=c;
}
insert(s);
}
while(n--){
int k;
cin>>k;
string s="";
for(int i=0;i<k;i++){
char c;
cin>>c;
s+=c;
}
cout<<query(s)<<"\n";
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t=1;
//cin>>t;
while(t--){
Showball();
}
return 0;
}
P6824 「EZEC-4」可乐 拆位+差分
考虑拆位,当前考虑 \(a_i\) 的第 \(j\) 位,前 \(j-1\) 位异或的结果相同。那么,对于第 \(j\) 位,一共有 \(4\) 种情况。
\(k\) 当前位为 \(1\),那么如果 \(x\) 选和 \(a_i\) 一样的,后面的位置就可以随便选了。如果选择不一样,就继续判断后面的位。
\(k\) 当前位为 \(0\)。那么 \(x\) 只能选 \(a_i\) 一样的,并且继续判断后面的位。
发现每次可选的 \(x\) 都有一个范围,因此我们可以进行差分维护。然后求覆盖次数最多的 \(x\) 即可。
#include<bits/stdc++.h>
using namespace std;
using i64=long long;
void Showball(){
int n,k;
cin>>n>>k;
vector<int> a(n);
for(int i=0;i<n;i++) cin>>a[i];
vector<int> c(1<<20);
for(int i=0;i<n;i++){
int cur=0;
for(int j=20;j>=0;j--){
int t=a[i]>>j&1;
if(k>>j&1){
if(t){
c[cur+(1<<j)]++;
c[cur+(1<<j+1)]--;
}else{
c[cur]++;
c[cur+(1<<j)]--;
}
cur+=(!t)*(1<<j);
}else{
cur+=t*(1<<j);
}
}
c[cur]++;c[cur+1]--;
}
int ans=c[0];
for(int i=1;i<1<<20;i++){
c[i]+=c[i-1];
ans=max(ans,c[i]);
}
cout<<ans<<"\n";
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t=1;
//cin>>t;
while(t--){
Showball();
}
return 0;
}
P3435 [POI2006] OKR-Periods of Words KMP
只需要求出每个前缀的最小前缀 \(Border\) 即可,然后用长度减去这个 \(Border\) 即可。记忆化一下,否则会 \(T\) 部分点。
#include<bits/stdc++.h>
using namespace std;
using i64=long long;
template<class Type>
struct KMP{
vector<int> init(Type s){
int n=(int)s.size()-1;
vector<int> nxt(n+1,0);
for(int i=2;i<=n;i++){
nxt[i]=nxt[i-1];
while(nxt[i]&&s[nxt[i]+1]!=s[i]) nxt[i]=nxt[nxt[i]];
nxt[i]+=(s[nxt[i]+1]==s[i]);
}
return nxt;
}
vector<int> match(Type s,Type t){
vector<int> pos;
vector<int> nxt=init(t);
int n=(int)s.size()-1,m=(int)t.size()-1;
for(int i=1,j=0;i<=n;i++){
while(j&&s[i]!=t[j+1]) j=nxt[j];
j+=(t[j+1]==s[i]);
if(j==m){//匹配成功
j=nxt[j];
pos.push_back(i-m+1);
}
}
return pos;
}
};
KMP<string> kmp;
void Showball(){
int n;
cin>>n;
string s;
cin>>s;
s="?"+s;
auto nxt=kmp.init(s);
i64 ans=0;
for(int i=1;i<=n;i++){
int j=i;
while(nxt[j]) j=nxt[j];
if(nxt[i]) nxt[i]=j;
ans+=i-j;
}
cout<<ans<<"\n";
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t=1;
//cin>>t;
while(t--){
Showball();
}
return 0;
}
P3369 【模板】普通平衡树
您需要动态地维护一个可重集合 \(M\),并且提供以下操作:
- 向 \(M\) 中插入一个数 \(x\)。
- 从 \(M\) 中删除一个数 \(x\)(若有多个相同的数,应只删除一个)。
- 查询 \(M\) 中有多少个数比 \(x\) 小,并且将得到的答案加一。
- 查询如果将 \(M\) 从小到大排列后,排名位于第 \(x\) 位的数。
- 查询 \(M\) 中 \(x\) 的前驱(前驱定义为小于 \(x\),且最大的数)。
- 查询 \(M\) 中 \(x\) 的后继(后继定义为大于 \(x\),且最小的数)。
#include<bits/stdc++.h>
using namespace std;
using i64=long long;
//Splay平衡树
#define ls(x) tr[x].ch[0]
#define rs(x) tr[x].ch[1]
const int N=1100010, INF=(1<<30)+1;
struct node{
int ch[2]; //儿
int fa; //父
int v; //点权
int cnt; //点权次数
int siz; //子树大小
void init(int p,int v1){
fa=p, v=v1;
cnt=siz=1;
}
}tr[N];
int root,tot; //根,节点个数
void pushup(int x){ //上传
tr[x].siz=tr[ls(x)].siz+tr[rs(x)].siz+tr[x].cnt;
}
void rotate(int x){ //旋转
int y=tr[x].fa, z=tr[y].fa, k=tr[y].ch[1]==x; //y的右儿是x
tr[z].ch[tr[z].ch[1]==y]=x, tr[x].fa=z; //z的儿是x,x的父是z
tr[y].ch[k]=tr[x].ch[k^1], tr[tr[x].ch[k^1]].fa=y; //y的儿是x的异儿,x的异儿的父是y
tr[x].ch[k^1]=y, tr[y].fa=x; //x的异儿是y,y的父是x
pushup(y), pushup(x); //自底向上push
}
void splay(int x, int k){ //伸展
while(tr[x].fa!=k){ //折线转xx,直线转yx
int y=tr[x].fa, z=tr[y].fa;
if(z!=k) (ls(y)==x)^(ls(z)==y)?rotate(x):rotate(y);
rotate(x);
}
if(!k) root=x; //k=0时,x转到根
}
void insert(int v){ //插入
int x=root, p=0;
//x走到空节点或走到目标点结束
while(x&&tr[x].v!=v) p=x,x=tr[x].ch[v>tr[x].v];
if(x) tr[x].cnt++; //目标点情况
else{ //空节点情况
x=++tot;
tr[p].ch[v>tr[p].v]=x;
tr[x].init(p,v);
}
splay(x, 0);
}
void find(int v){ //找到v并转到根
int x=root;
while(tr[x].ch[v>tr[x].v]&&v!=tr[x].v)
x=tr[x].ch[v>tr[x].v];
splay(x, 0);
}
int getpre(int v){ //前驱
find(v);
int x=root;
if(tr[x].v<v) return x;
x=ls(x);
while(rs(x)) x=rs(x);
splay(x, 0);
return x;
}
int getsuc(int v){ //后继
find(v);
int x=root;
if(tr[x].v>v) return x;
x=rs(x);
while(ls(x)) x=ls(x);
splay(x, 0);
return x;
}
void del(int v){ //删除
int pre=getpre(v);
int suc=getsuc(v);
splay(pre,0), splay(suc,pre);
int del=tr[suc].ch[0];
if(tr[del].cnt>1)
tr[del].cnt--, splay(del,0);
else
tr[suc].ch[0]=0, splay(suc,0);
}
int getrank(int v){ //排名
insert(v);
int res=tr[tr[root].ch[0]].siz;
del(v);
return res;
}
int getval(int k){ //数值
int x=root;
while(true){
if(k<=tr[ls(x)].siz) x=ls(x);
else if(k<=tr[ls(x)].siz+tr[x].cnt) break;
else k-=tr[ls(x)].siz+tr[x].cnt, x=rs(x);
}
splay(x, 0);
return tr[x].v;
}
void Showball(){
insert(-INF);insert(INF); //哨兵
int n;
cin>>n;
while(n--){
int op,x;
cin>>op>>x;
if(op==1){
insert(x);
}else if(op==2){
del(x);
}else if(op==3){
cout<<getrank(x)<<"\n";
}else if(op==4){
cout<<getval(x+1)<<"\n";
}else if(op==5){
cout<<tr[getpre(x)].v<<"\n";
}else{
cout<<tr[getsuc(x)].v<<"\n";
}
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t=1;
//cin>>t;
while(t--){
Showball();
}
return 0;
}
P4592 [TJOI2018] 异或 可持久化Trie+树链剖分
一道模板题,解决区间异或最大值--可持久化Trie。然后用树链剖分把树上问题转化成链上问题即可。
注意插入的时候要按照 \(dfs\) 序插入Trie中。
#include<bits/stdc++.h>
using namespace std;
using i64=long long;
const int N =1e5+10;
int a[N];
//可持久化Trie
struct Trie{
int ch[N<<5][2];
int rt[N];
int sz[N<<5];
int idx=0,cnt=0;
void insert(int v){
rt[++idx]=++cnt;//新根开点
int x=rt[idx-1];//旧版
int y=rt[idx];
for(int i=30;i>=0;i--){
int j=v>>i&1;
ch[y][j^1]=ch[x][j^1];//异位继承
ch[y][j]=++cnt;//新位
x=ch[x][j];y=ch[y][j];
sz[y]=sz[x]+1;
}
}
int query(int x,int y,int v){
int ret=0;
for(int i=30;i>=0;i--){
int j=(v>>i&1);
if(sz[ch[y][j^1]]>sz[ch[x][j^1]])
x=ch[x][j^1],y=ch[y][j^1],ret|=(1LL<<i);
else
x=ch[x][j],y=ch[y][j];
}
return ret;
}
}trie;
//树链剖分
vector<int> e[N];
int fa[N],dep[N],son[N],sz[N],id[N],w[N];
int top[N],dfn;
//预处理各个信息
void dfs1(int u,int father){
fa[u]=father;
dep[u]=dep[father]+1;
sz[u]=1;
for(auto v:e[u]){
if(v==father) continue;
dfs1(v,u);
sz[u]+=sz[v];
if(sz[son[u]]<sz[v]) son[u]=v;
}
}
void dfs2(int u,int tp){
id[u]=++dfn;
w[dfn]=a[u];
top[u]=tp;
if(!son[u]) return;
dfs2(son[u],tp);
for(auto v:e[u]){
if(v==fa[u]||v==son[u]) continue;
dfs2(v,v);
}
}
int qRange(int u,int v,int val){
int res=0,l,r;
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
//线段树区间查询[id[top[u]],id[u]]
l=trie.rt[id[top[u]]-1];
r=trie.rt[id[u]];
res=max(res,trie.query(l,r,val));
u=fa[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
//加上两点之间的答案[id[u],id[v]]
l=trie.rt[id[u]-1];
r=trie.rt[id[v]];
res=max(res,trie.query(l,r,val));
return res;
}
int qSon(int u,int v){
//线段树查询[id[u],id[u]+sz[u]-1]
int l=trie.rt[id[u]-1],r=trie.rt[id[u]+sz[u]-1];
return trie.query(l,r,v);
}
void Showball(){
int n,q;
cin>>n>>q;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
e[u].push_back(v);
e[v].push_back(u);
}
dfs1(1,-1);
dfs2(1,1);
vector<int> p(n+1);
iota(p.begin(),p.end(),0);
sort(p.begin()+1,p.end(),[&](int x,int y){
return id[x]<id[y];
});
for(int i=1;i<=n;i++){
trie.insert(a[p[i]]);
}
while(q--){
int op;
cin>>op;
if(op==1){
int x,z;
cin>>x>>z;
cout<<qSon(x,z)<<"\n";
}else{
int x,y,z;
cin>>x>>y>>z;
cout<<qRange(x,y,z)<<"\n";
}
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t=1;
//cin>>t;
while(t--){
Showball();
}
return 0;
}
P5283 [十二省联考 2019] 异或粽子 01-Trie + 堆
求前 \(k\) 大区间异或和,经典套路,考虑先维护前缀异或和。那么问题变成求 \(n+1\) 个数求两两异或前 \(k\) 大。
前置芝士--序列合并 两个数组两两求和前 \(k\) 大。排序后,将 \(a\) 数组的每个数和 \(b\) 数组首项的全部放进堆中。
然后每次取出堆头元素,然后将堆头元素对应的 \(a\) 数组值和 对应的 \(b\) 数组 的下一个数组值的和加入堆中。
然后做 \(k\) 次即可。本质其实就是构成了 \(n\) 个有序序列进行合并。
#include<bits/stdc++.h>
using namespace std;
using i64=long long;
struct node{
int x,y,z;
bool operator<(const node u)const{
return z>u.z;
}
};
void Showball(){
int n;
cin>>n;
vector<int> a(n),b(n);
for(int i=0;i<n;i++) cin>>a[i];
for(int i=0;i<n;i++) cin>>b[i];
priority_queue<node> pq;
for(int i=0;i<n;i++){
pq.push({i,0,a[i]+b[0]});
}
while(n--){
auto u=pq.top();
cout<<u.z<<" ";
pq.pop();
pq.push({u.x,u.y+1,a[u.x]+b[u.y+1]});
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t=1;
//cin>>t;
while(t--){
Showball();
}
return 0;
}
那么本题也可以用同样的思路。我们借助 01-Trie
可以求出 与 \(s_i\) 异或第 \(k\) 大的值。
一开始将所有右端点以及异或最大值放到堆中,然后每次取出堆顶值,并且将次大值放进堆中即可。
注意这里要求 \(l<r\) ,所以我们直接全部计算,最后答案除以 \(2\) 即可。
#include<bits/stdc++.h>
using namespace std;
using i64=long long;
const int N=5e5+10;
int ch[N*33][2],idx;
int sz[N*33];
void ins(i64 x){
for(int i=32,p=0;i>=0;i--){
int j=x>>i&1;
if(!ch[p][j]) ch[p][j]=++idx;
p=ch[p][j];
sz[p]++;
}
}
void del(i64 x){
for(int i=32,p=0;i>=0;i--){
int j=x>>i&1;
p=ch[p][j];
--sz[p];
}
}
i64 query(i64 x,int rk){
i64 ret=0;
for(int i=32,p=0;i>=0;i--){
int j=((x>>i)&1);
if(!ch[p][j^1]) p=ch[p][j];
else if(rk<=sz[ch[p][j^1]]){
p=ch[p][j^1];
ret|=1LL<<i;
}else{
rk-=sz[ch[p][j^1]];
p=ch[p][j];
}
}
return ret;
}
struct node{
int id;
int rk;
i64 val;
bool operator<(const node &u)const{
return val<u.val;
}
};
void Showball(){
int n,k;
cin>>n>>k;
k<<=1;
vector<i64> a(n+1);
for(int i=1;i<=n;i++){
i64 x;
cin>>x;
a[i]=a[i-1]^x;
}
priority_queue<node> pq;
for(int i=0;i<=n;i++) ins(a[i]);
for(int i=0;i<=n;i++){
pq.push({i,1,query(a[i],1)});
}
i64 ans=0;
while(k--){
node u=pq.top();
pq.pop();
ans+=u.val;
int id=u.id,rk=u.rk;
if(rk+1<=n){
pq.push({id,rk+1,query(a[id],rk+1)});
}
}
cout<<ans/2<<"\n";
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t=1;
//cin>>t;
while(t--){
Showball();
}
return 0;
}