[USACO6.3.1 COWXOR] Trie 2进制分解

题目:

http://www.wzoi.org/usaco/16%5C107.asp

总的来说就是给定一个数列(n<=100000)。

求一段连续的子数列,使得其中的数都xor在一起以后,得到的值最大。

分析:

自己想的原创算法吖

首先我们先分析一下xor这个运算的性质。

易得y=y xor t xor t(即偶数次xor同一个数,结果和原来一样)

于是我们可以设s[i]表示从a[1]xor到a[i]的值。(定义s[0]=0)

如果我们需要某一段[i,j]的xor值,就可以这样求s[j] xor s[i-1],因为a[1]到a[i-1]的数被xor了两次!

这样,问题转化为:求max(s[i] xor s[j]),其中i∈[0,j],j∈[1,n]。

于是现在要求的是取两个数的xor值最大!

首先我们应该明白,对于一个数字,我们可以把它看成一个长度为20的字符串(题目给的a[i]<=2^21-1)!

那么我们怎么比较这些字符串的大小呢?

很明显,我们是从首位扫到末尾!

于是假设我们现在枚举j,即已经确定了s[j],然后在[0,j]的范围类找一个s[i] xor s[j]最大。

因为是当成字符串,我们用Trie树来储存那些数字!

然后从首位开始

1、如果我这个s[j]的这一位是0,那么另外一个配对最好是1,

因为这样xor会得到1。如果找不到那就只能到0那边去。

2、如果我这个s[j]的这一位是1,那么另外一个配对最好是0,

理由同上。

因为越前的位在大小上影响更大,所以我们这样就贪心的走一遍Trie树,

所得的结果就是和s[j]配对最大的s[i]。

然后统计每个j对应的最大值,输出结果即可。

HINT

UASCO给的空间比较小,用指针可以过,但静态空间的Trie就不能开到极限那么大,开小一点就能过。

code:

/*
ID:jie22221
TASK:cowxor
LANG:C++
*/
#include const int N=111111;
struct treetype{int l,r,dep;}T[5*N];
struct gtp{int x,y,next;}g[N];
int n,a[N],s[N],ans=-1,ansi=0,ansj=0,tot,root,e=0,ls[5*N];

void addedge(int x,int y){
       e++;
       g[e].x=x; g[e].y=y; g[e].next=ls[x]; ls[x]=e;
}

void add(int &p,int x,int dep,int link){
     if (p==0){
       tot++;
       p=tot;
       T[p].l=0;
       T[p].r=0;
       T[p].dep=dep;
     }
     if (dep==-1){
       addedge(p,link);
       return;
     }
     if (((1<                     else add(T[p].l,x,dep-1,link);
}

void init(){
     scanf("%d",&n);
     for (int i=1;i<=n;i++) scanf("%d",&a[i]);
     for (int i=1;i<=n;i++){
         s[i]=s[i-1]^a[i];
         if (a[i]>ans){
           ans=a[i];
           ansi=i;
           ansj=i;
         }
         if (s[i]>ans){
           ans=s[i];
           ansi=1;
           ansj=i;
         }
     }
     tot=1;
     root=1;
     T[1].l=0;
     T[1].r=0;
     T[1].dep=22;
}

inline bool flag(int x,int y,int z){
       if (x>ans) return true;
       if (x       if (z       if (z>ansj) return false;
       if (z-y       return false;
}

void work(){
     add(root,s[1],22,1);
     for (int i=2;i<=n;i++){
         int p=root,now=0;
         for (int j=22;j>=0;j–)
           if ((s[i]|(1<               if (T[p].l!=0) {p=T[p].l; now|=1<                         else p=T[p].r;
             }
           else {
               if (T[p].r!=0) {p=T[p].r; now|=1<                         else p=T[p].l;
               }
           if (flag(now,g[ls[p]].y+1,i)){
              ans=now;
              ansi=g[ls[p]].y+1;
              ansj=i;
           }
         add(root,s[i],22,i);
     }
}

void print(){
     printf("%d %d %dn",ans,ansi,ansj);
}

int main(){
    freopen("cowxor.in","r",stdin);
    freopen("cowxor.out","w",stdout);
    init();
    work();
    print();
    return 0;
}

POJ 3261 后缀数组 分组思想

题意:

给出一个串,求这个串的最长的出现过至少k次的子串。出现的位置可以重叠。

分析:

用后缀数组先处理出height数组,然后二分答案,根据limit将height分组,使得每一组里任意串的lcp>=limit。

然后如果某一组的串的个数超过>=k,那么flag=true。

插曲:

基数排序时e没有设为0,WA了和RE了好多遍。

code:

#include

POJ 1743 后缀数组 分组思想 Sparse_Table (RMQ) 男人8题

题意:

楼教主男人8题里的一题。

给出一串数,让你找出最长的两段不重合的连续差值相等的子串。

分析:

先令S[I]=A[I]-A[I-1],然后问题转化为求S[I]的最长不重合重复子串。

先用后缀数组处理好S[I],然后二分答案分组,如果某一组里的MAX(SA[I])-MIN(SA[J])>LIMIT,表示这组可以找到不重合的答案。

然后输出即可。

插曲:

有一个地方Gn打成n了。WA了N久。

code:

#include #include #define max(x,y) (x)>(y)?(x):(y)
#define min(x,y) (x)<(y)?(x):(y)
const int N=22222;
struct gtp{int x,data[3],next;}g[N];
int n,s[N],sa[N],rank[N],height[N],a[N][3],ls[N],e;
int G[N],pu[20][N],pd[20][N],tmp[N],lg[N];

void init(){
    for (int i=0;i    for (int i=1;i    n–;
    int t=0; lg[1]=0;
    for (int i=1;i<=n;i++)
      if (1<                else lg[i]=lg[i-1];
}

inline void add(int x,int data[]){
    e++;
    g[e].x=x; g[e].next=ls[x]; ls[x]=e;
    memcpy(g[e].data,data,sizeof(a[0]));
}

void Jsort(){
    for (int k=1;k>=0;k–){
        e=0;
        for (int i=0;i<=n;i++) ls[i]=0;
        for (int i=n;i>=1;i–) add(a[i][k],a[i]);
        int tail=0;
        for (int i=0;i<=n;i++)
          for (int t=ls[i];t!=0;t=g[t].next)
              memcpy(a[++tail],g[t].data,sizeof(a[0]));
    }
}

void Sort(int l,int r){
    if (l>=r) return;
    int i=l,j=r,mid=s[sa[l+r>>1]],temp;
    while (i        while (s[sa[i]]        while (s[sa[j]]>mid) j–;
        if (i<=j){
            temp=sa[i]; sa[i]=sa[j]; sa[j]=temp;
            i++; j–;
        }
    }
    Sort(l,j);
    Sort(i,r);
}

void build(){
    for (int i=1;i<=n;i++) sa[i]=i;
    Sort(1,n);
    int tail=0;
    for (int i=1;i<=n;i++){
        if (i==1 || s[sa[i]]!=s[sa[i-1]]) tail++;
        rank[sa[i]]=tail;
    }
    for (int k=1;1<        for (int i=1;i<=n;i++){
            a[i][0]=rank[i];
            if (i+(1<                          else a[i][1]=0;
            a[i][2]=i;
        }
        Jsort();
        tail=0;
        for (int i=1;i<=n;i++){
            if (i==1 || a[i][0]!=a[i-1][0] || a[i][1]!=a[i-1][1]) tail++;
            rank[a[i][2]]=tail;
        }
    }
    for (int i=1;i<=n;i++) sa[rank[i]]=i;
}

void Sparse_Table(){
    for (int i=1;i<=n;i++) pu[0][i]=sa[i];
    for (int i=1;i<=n;i++) pd[0][i]=sa[i];
    for (int k=1;1<      for (int i=1;i+(1<        pu[k][i]=max(pu[k-1][i],pu[k-1][i+(1<        pd[k][i]=min(pd[k-1][i],pd[k-1][i+(1<      }
}

inline int Maxsa(int l,int r){
    int t=lg[r-l+1];
    return max(pu[t][l],pu[t][r-(1<}

inline int Minsa(int l,int r){
    int t=lg[r-l+1];
    return min(pd[t][l],pd[t][r-(1<}

void MakeHeight(){
    for (int i=0;i<=n;i++) height[i]=0;
    for (int i=1;i<=n;i++){
        if (rank[i]==1) continue;
        int st=max(height[rank[i-1]]-1,0),j=i+st,k=sa[rank[i]-1]+st;
        while (j<=n && k<=n && s[j]==s[k]){st++;j++;k++;}
        height[rank[i]]=st;
    }
}

bool flag(int limit){
    int Gn=1; G[1]=1;
    for (int i=2;i<=n;i++)
      if (height[i]>=limit) G[i]=Gn;
                       else G[i]=++Gn;
    int j=1;
    for (int i=1;i<=Gn;i++){
        int st=j;
        while (j<=n && G[j]==i) j++;
        int ed=j-1;
        if (Maxsa(st,ed)-Minsa(st,ed)>limit) return true;
    }
    return false;
}

int binary(){
    int l=0,r=n,mid;
    while (l+1        mid=l+r>>1;
        if (flag(mid)) l=mid;
                  else r=mid-1;
    }
    if (flag(r)) return r;
    return l;
}

int main(){
    while (scanf("%d",&n) && n!=0){
        init();
        build();
        Sparse_Table();
        MakeHeight();
        int ans=binary();
        if (ans<4) printf("0n");
              else printf("%dn",ans+1);
    }
}

POJ 3294 后缀数组 分组思想

题意:

找出最长的串,使其是在给定的N个串中,超过N/2个得子串。

如果有多个最长的串,那么按字典序输出所有的串。

分析:

先把所有串串起来,期间用不同的特殊字符间隔。

这里是为了保证后缀数组不会隔着组弄成lcp。

这题可以先用后缀数组处理出height数组。

然后二分答案(长度)。

Next,按顺序将height分组(每组是连续的),使得每一组里除了第一以外的height值都>=limit

如果里面有一组包含超过N/2个所归属的串,那么flag=true

如果flag=true,那么放大答案,否则减小。

最后按顺序取来输出就行了。

插曲:

1A

这次后缀数组部分短了大概50行= =,但还是比较长。

code:

#include #include #include using namespace std;
const int N=200000;
struct gtp{int x,data[3],next;}g[N];
char s[N],ts[N];
int n,sa[N],rank[N],height[N],len,v[N],a[N][3],ls[N],e,G[N];
int mark[N];

void init(){
    memset(s,0,sizeof(s));
    memset(v,0,sizeof(v));
    memset(sa,0,sizeof(sa));
    memset(rank,0,sizeof(rank));
    memset(height,0,sizeof(height));
    len=0;
    char tmp=’A’;
    for (int i=1;i<=n;i++){
        scanf("%s",&ts);
        int tlen=strlen(ts);
        for (int j=0;j          s[++len]=ts[j];
          v[len]=i;
        }
        s[++len]=tmp;
        tmp++;
        if (tmp>’Z’) tmp=’A’;
    }
}

void Sort(int l,int r){
    if (l>=r) return;
    int i=l,j=r; char mid=s[sa[l+r>>1]];
    while (i        while (s[sa[i]]        while (s[sa[j]]>mid) j–;
        if (i<=j){
            swap(sa[i],sa[j]);
            i++; j–;
        }
    }
    Sort(l,j);
    Sort(i,r);
}

inline void add(int x,int data[]){
    e++;
    g[e].x=x; g[e].next=ls[x]; ls[x]=e;
    memcpy(g[e].data,data,sizeof(a[0]));
}

void Jsort(){
    for (int k=1;k>=0;k–){
        e=0;
        memset(ls,0,sizeof(int)*(len+1));
        for (int i=len;i>=1;i–) add(a[i][k],a[i]);
        int tail=0;
        for (int i=0;i<=len;i++)
          for (int t=ls[i];t!=0;t=g[t].next)
            memcpy(a[++tail],g[t].data,sizeof(a[0]));
    }
}

void buildarr(){
    for (int i=1;i<=len;i++) sa[i]=i;
    Sort(1,len);
    int tail=0;
    for (int i=1;i<=len;i++){
        if (i==1 || s[sa[i]]!=s[sa[i-1]]) tail++;
        rank[sa[i]]=tail;
    }
    for (int k=2;k<=len;k<<=1){
        for (int i=1;i<=len;i++){
            a[i][0]=rank[i];
            if (i+(k>>1)<=len) a[i][1]=rank[i+(k>>1)];
                          else a[i][1]=0;
            a[i][2]=i;
        }
        Jsort();
        tail=0;
        for (int i=1;i<=len;i++){
            if (i==1 || a[i][0]!=a[i-1][0] || a[i][1]!=a[i-1][1]) tail++;
            rank[a[i][2]]=tail;
        }
    }
    for (int i=1;i<=len;i++) sa[rank[i]]=i;
}

void GetHeight(){
    for (int i=1;i<=len;i++){
        if (rank[i]==1) continue;
        int st=max(0,height[rank[i-1]]-1),j=i+st,k=sa[rank[i]-1]+st;
        while (j<=len && k<=len && s[j]==s[k]){st++; j++; k++;}
        height[rank[i]]=st;
    }
}

bool flag(int limit){
    bool ret=false;
    int Gn=1; G[1]=1;
    for (int i=2;i<=len;i++)
      if (height[i]>=limit) G[i]=Gn;
                       else G[i]=++Gn;
    memset(mark,0,sizeof(int)*(len+1));
    int j=1,num;
    for (int i=1;i<=Gn;i++){
        num=0;
        while (j<=len && G[j]==i) {
            if (v[sa[j]]!=0 && mark[v[sa[j]]]!=i){
                mark[v[sa[j]]]=i;
                num++;
            }
            j++;
        }
        if (num>n/2) ret=true;
    }
    return ret;
}

int binary(){
    int l=0,r=len,mid;
    while (l+1        mid=l+r>>1;
        if (flag(mid)) l=mid;
                  else r=mid-1;
    }
    if (flag(r)) return r;
    return l;
}

void print(int ans){
    if (ans==0) printf("?n");
    else{
        int Gn=1; G[1]=1;
        for (int i=2;i<=len;i++)
          if (height[i]>=ans) G[i]=Gn;
                         else G[i]=++Gn;
        memset(mark,0,sizeof(int)*(len+1));
        int j=1,num,st;
        for (int i=1;i<=Gn;i++){
          num=0;
          st=j;
          while (j<=len && G[j]==i) {
              if (v[sa[j]]!=0 && mark[v[sa[j]]]!=i){
                  mark[v[sa[j]]]=i;
                  num++;
              }
              j++;
          }
          if (num>n/2){
              for (int k=0;k                printf("%c",s[sa[st]+k]);
              printf("n");
          }
        }
    }
}

int main(){
    scanf("%dn",&n);
    while (n!=0){
        init();
        buildarr();
        GetHeight();
        int ans=binary();
        print(ans);
        scanf("%dn",&n);
        if (n!=0) printf("n");
    }
    return 0;
}

后缀数组学习笔记

首先定义:

rank[i]为S[i..n]这个后缀是所有后缀里第几大的。

sa[i]为第i大的后缀是哪一个

因为后缀不可能相等,所以rank[i]和sa[i]唯一,且满足sa[rank[i]]=i

我们可以通过类似多关键字排序+构造Sparse Table的方法使构造这两个数组的时间复杂度为O(nlgn)

注意中间过程用基数排序,否则用qsort的话会变成O(n(lgn)^2)

构造完这个,我们还应该加一个height数组,height[i]表示lcp(i-1,i)。

其中i表示排名,即rank[sa[i]]=i

这样就相当于后缀树中两个相邻节点的lca了。

至于如何构造height[i],这里有我写的code。

其中使用了一个定理:

height[i]>=height[sa[i]-1]-1(看懂什么意思以后,证明显然)

使用这个定理以后构造height的复杂度降到了O(n)

void lcp(){
     memset(height,0,sizeof(height));
     for (int i=1;i<=n;i++){
         if (rank[i]==1) continue;
         int st,j,k;
         st=max(height[rank[i-1]]-1,0);
         j=i+st;
         k=sa[rank[i]-1]+st;
         while (j<=n && k<=n && s[j]==s[k]){
               st++;
               j++;
               k++;
         }
         height[rank[i]]=st;
     }
}

另外再有一个定理lcp(i,j)=min(lcp(k-1,k)) (k∈(i,j])

姑且叫他lcp(i,j)定理

然后我们就可以用它解决很多问题。

总的来说有:

1、多串匹配。

复杂度O( (T+lgN)*M)   其中T为模式串长度,N为主串长度,M为模式串个数。

主要思想就是二分,然后利用height数组,和lcp(i,j)定理。

2、最长公共前缀。

例题:http://hi.baidu.com/edwardmj/blog/item/a69c46560990d5143a2935fb.html

就是max(height[i])

3、最长回文子串。

设给定S’,求其最长回文子串。(设其长度为n)

令S”为S’的倒序串(即S”[n-i+1]=S'[i])

然后S=S’+’#’+S”。(即把两串连接)(长度为2*n+1)

然后枚举中心i(1<=i<=n),其镜像中心为2*n+2-i

然后就是求最多能延伸多长。

即求lcp(i,i’)。利用lcp(i,j)定理加上Sparse Table(RMQ)就可以做到在O(1)的复杂度内解决延伸问题。

所以,求最长回文子串的部分,复杂度为O(N)

总复杂度O(nlgn)(加上构造时间)

4、利用分组思想做一些意想不到的操作.

例如:http://hi.baidu.com/edwardmj/blog/item/e5105b8d97842ef1513d92ed.html