洛谷题单 算法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 字典树

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\),并且提供以下操作:

  1. \(M\) 中插入一个数 \(x\)
  2. \(M\) 中删除一个数 \(x\)(若有多个相同的数,应只删除一个)。
  3. 查询 \(M\) 中有多少个数比 \(x\) 小,并且将得到的答案加一。
  4. 查询如果将 \(M\) 从小到大排列后,排名位于第 \(x\) 位的数。
  5. 查询 \(M\)\(x\) 的前驱(前驱定义为小于 \(x\),且最大的数)。
  6. 查询 \(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;
}
posted @ 2024-12-10 01:55  Showball  阅读(3)  评论(0编辑  收藏  举报