BZOJ1857:[Scoi2010]传送带 三分

思路:

显然一条比较优的路径是从\(A\)走到\(AB\)上一点\(X\),然后从\(X\)走到\(CD\)上一点\(Y\),然后从\(Y\)走到\(D\).

关键是\(X,Y\)如何确定.

我们能够发现\(X\)点确定时,随着\(Y\)的移动,总路程是一个单峰函数.

更加神奇的是,随着\(X\)的移动,通过我们上述三分得到的总路程也是一个单峰函数!

没什么说的,三分套三分就行了.

#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
 
typedef double f2;
 
#define eps 1e-8
struct Point{
    f2 x,y;
    void read(){scanf("%lf%lf",&x,&y);}
    Point(){}
    Point(f2 _x,f2 _y):x(_x),y(_y){}
    Point operator-(const Point&B)const{return Point(B.x-x,B.y-y);}
    Point operator+(const Point&B)const{return Point(x+B.x,y+B.y);}
    Point operator*(const f2&p)const{return Point(p*x,p*y);}
}A,B,C,D;
 
int vab,vcd,vplane;
 
f2 sqr(const f2&x){return x*x;}
f2 Dist(const Point&A,const Point&B){return sqrt(sqr(A.x-B.x)+sqr(A.y-B.y));}
f2 f(Point x,Point l,Point v,f2 k){
    return Dist(x,l+v*k)/vplane+Dist(l+v*k,l+v)/vcd;
}
f2 Solve(Point x,Point l,Point r){
    Point v=l-r;
    f2 L=0,R=1,LL,RR,ansl,ansr,lastans=1e12,nowans;
    while(1){
        LL=L+(R-L)/3,RR=R-(R-L)/3;
        ansl=f(x,l,v,LL),ansr=f(x,l,v,RR);
        if(ansl<ansr)R=RR,nowans=ansl;else L=LL,nowans=ansr;
        if(fabs(nowans-lastans)<1e-5)break;lastans=nowans;
    }
    return lastans;
}
 
int main(){
    A.read(),B.read(),C.read(),D.read();
    scanf("%d%d%d",&vab,&vcd,&vplane);
    f2 L=0,R=1,LL,RR,ansl,ansr,lastans=1e12,nowans;Point v=A-B;
    while(1){
        LL=L+(R-L)/3,RR=R-(R-L)/3;
        ansl=Dist(A,A+v*LL)/vab+Solve(A+v*LL,C,D),ansr=Dist(A,A+v*RR)/vab+Solve(A+v*RR,C,D);
        if(ansl<ansr)R=RR,nowans=ansl;else L=LL,nowans=ansr;
        if(fabs(nowans-lastans)<1e-5)break;lastans=nowans;
    }
    printf("%.2lf",lastans);
    return 0;
}

BZOJ1811:[Ioi2005]mea 乱搞

思路:发现若\(S_1\)不同,则整个序列就不同.因此我们的任务就是找出有多少种\(S_1\)的方案.

我们把\(S_2...S_{n+1}\)都用关于\(S_1\)的一次函数表示,则我们有\(n\)个线性约束条件,对于第\(i\)个条件,我们有\(S_{i}\leq{S_{i+1}}\).

直接接出\(S_1\)的上下界即可.

#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
using namespace std;
 
typedef long long LL;
 
#define N 5000010
int k[N];LL b[N];
 
int main(){
    int n;scanf("%d",&n);register int i;LL a;
    k[1]=1,b[1]=0;
    for(i=2;i<=n+1;++i)scanf("%lld",&a),k[i]=-k[i-1],b[i]=2LL*a-b[i-1];
    LL up=1LL<<60,down=-1LL<<60;
    for(i=1;i<=n;++i){
        if(k[i]==1)up=min(up,b[i+1]-b[i]);
        else down=max(down,b[i]-b[i+1]);
    }
    if(up>0)up/=2;else{if(up&1)up=(up-1)/2;else up/=2;}
    if(down>0){if(down&1)down=(down+1)/2;else down/=2;}else down/=2;
    if(up<down){puts("0");return 0;}else printf("%lld",up-down+1);
    return 0;
}

BZOJ2600:[Ioi2011]ricehub 贪心+二分

思路:

显然当我们选定一个米仓的位置后,选择的粮田的位置是一段连续区间.

我们不妨枚举枚举所有的粮田区间,于是我们发现在给定粮田区间时,最优的米仓位置必定是中位数,可以\(O(1)\)确定.

若我们枚举所有的区间,则是\(O(R^2)\)的.

于是我们考虑固定区间起点,则需要的花费显然随区间终点右移而单调不减.于是我们二分得到最优的区间终点即可.

时间复杂度\(O(nlogn)\).

#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
using namespace std;
 
typedef long long LL;
 
#define N 100010
int a[N];
LL sum[N];
 
long long getsum(int l,int r){return sum[r]-sum[l-1];}
long long getinterval(int l,int r){
    if(l==r)return 0;
    if((r-l+1)&1){
        int midins=(l+r)>>1;
        return getsum(midins+1,r)-getsum(l,midins-1);
    }
    else{
        int midins=(l+r)>>1;
        return getsum(midins+1,r)-getsum(l,midins);
    }
}
 
int main(){
    int R,L;LL B;scanf("%d%d%lld",&R,&L,&B);
    register int i,j;for(i=1;i<=R;++i)scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i];
     
    int l,r,mid,res=0;
    for(i=1;i<=R;++i){
        l=i,r=R;while(l<r){mid=(l+r+1)>>1;if(getinterval(i,mid)<=B)l=mid;else r=mid-1;}res=max(res,l-i+1);
    }
     
    printf("%d",res);
     
    return 0;
}

3544:[ONTAK2010]Creative Accounting 贪心+迷の卡常数

思路:

维护一下前缀和对\(M\)取模得到的值.

考虑当一段区间的结尾固定时,如何寻找区间的起始端点才能使得答案最优.

首先是减去一个比当前小的数,为了使答案最大,当然是减去0,也就是用当前的模来更新答案;

其次是减去一个比当前大的数,这个需要加上\(M\),由于贪心的使答案最大,我们就减去一个之前出现过的比当前大的最小的模来更新答案.这东西用一个set维护就行.

然后我被迷の卡常数了QoQ.

这样写:upper_bound(s.begin(),s.end(),sum[i])就TLE.(T的不是一点半点)

这样写:s.upper_bound(sum[i])就AC.

这TM是什么鬼?

#include<cstdio>
#include<cstring>
#include<cctype>
#include<climits>
#include<set>
#include<iostream>
#include<algorithm>
using namespace std;
 
typedef long long LL;
 
namespace Fio{
    inline int getc(){
        static const int L=1<<15;static char buf[L],*S=buf,*T=buf;
        if(S==T){T=(S=buf)+fread(buf,1,L,stdin);if(S==T)return EOF;}
        return*S++;
    }
    template<typename T>inline void Getunsign(T&x){
        int c;while(!isdigit(c=getc()));x=c-'0';while(isdigit(c=getc()))x=(x<<1)+(x<<3)+c-'0';
    }
    template<typename T>inline void Get(T&x){
        int c;while(!isdigit(c=getc())&&c!='-');bool sign=c=='-';
        x=sign?0:c-'0';while(isdigit(c=getc()))x=(x<<1)+(x<<3)+c-'0';
        if(sign)x=-x;
    }
}
 
#define N 200010
LL a[N],sum[N],p;
set<LL>s;
 
int main(){
    //freopen("tt.in","r",stdin);
    using namespace Fio;
    int n;Getunsign(n);Getunsign(p);
    register int i,j;for(i=1;i<=n;++i){Get(a[i]),a[i]%=p;if(a[i]<0)a[i]+=p;}
    for(i=1;i<=n;++i){sum[i]=sum[i-1],sum[i]+=a[i];if(sum[i]>=p)sum[i]-=p;}
     
    LL nowans=-1;
    for(i=1;i<=n;++i){
        if(sum[i]>nowans)nowans=sum[i];
        set<LL>::iterator it=s.upper_bound(sum[i]);
        if(it!=s.end()&&nowans<sum[i]-*it+p)nowans=sum[i]-*it+p;
        s.insert(sum[i]);
    }
     
    printf("%lld",nowans);
     
    return 0;
}

BZOJ3866:The Romantic Hero dp

思路:

令\(f[i][j]\)表示最后一个数的位置\(\leq{i}\),异或起来等于\(j\)的序列数,\(g[i][j]\)表示第一个数的位置\(\geq{i}\),与起来等于\(j\)序列数.

我们差分之后就很容易得到结尾、开头位置确定的异或、与的序列个数了.

这两个东西知道之后,随便维护个前缀和什么的就行了.

(大常数\(O(n^2)\),目前倒数第一)

#include<cstdio>
#include<cstring>
#include<cctype>
#include<climits>
#include<iostream>
#include<algorithm>
using namespace std;
  
#define N 1010
#define M 1024
int f[N][M],g[N][M],a[N],add[M][N];
  
static const int mod=(1e9)+7;
  
inline void inc(int&x,int y){if((x+=y)>=mod)x-=mod;}
inline int dec(int x,int y){return (x-y+mod)%mod;}
int main(){
    int T;scanf("%d",&T);
    register int i,j;int n;
    while(T--){
        scanf("%d",&n);for(i=1;i<=n;++i)scanf("%d",&a[i]);
        for(memset(f,0,sizeof f),i=1;i<=n;++i){
            inc(f[i][a[i]],1);
            for(j=0;j<M;++j)inc(f[i][a[i]^j],f[i-1][j]);
            for(j=0;j<M;++j)inc(f[i][j],f[i-1][j]);
        }
        for(memset(g,0,sizeof g),i=n;i>=1;--i){
            inc(g[i][a[i]],1);
            for(j=0;j<M;++j)inc(g[i][a[i]&j],g[i+1][j]);
            for(j=0;j<M;++j)inc(g[i][j],g[i+1][j]);
        }
          
        for(i=n;i>=1;--i)for(j=0;j<M;++j)f[i][j]=dec(f[i][j],f[i-1][j]);
        for(i=1;i<=n;++i)for(j=0;j<M;++j)g[i][j]=dec(g[i][j],g[i+1][j]);
          
        for(j=0;j<M;++j)for(i=n;i>=1;--i)add[j][i]=add[j][i+1],inc(add[j][i],g[i][j]);
          
        int ans=0;
        for(j=0;j<M;++j)for(i=1;i<=n;++i)inc(ans,(long long)f[i][j]*add[j][i+1]%mod);
          
        printf("%d\n",ans);
    }
      
    return 0;
}

BZOJ3277:串 后缀自动机+离线处理+树状数组(&&BZOJ3473)

思路:

水题一道.

首先建立广义后缀树.

然后利用离线+树状数组搞出每一个节点在多少个串中.

然后如果这个节点在不少于\(k\)个串中,我们令这个结点的权值为这个节点父亲边的字符个数,否则为0.

随后我们预处理一下从根到每个节点路径上的权值和.

于是每个字符串的答案等于所有这个字符串的后缀节点的从根到该节点的权值和.

时间复杂度\(O(nlogn)\).

(貌似比一些后缀数组的神方法要好多了QoQ)

#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
using namespace std;
 
#define N 100010
 
int tranc[N<<1][26],len[N<<1],pa[N<<1],cnt,root,last;
inline int newnode(int l){len[++cnt]=l;return cnt;}
 
struct Graph{
    int head[N<<1],next[N<<1],end[N<<1],ind;
    inline void addedge(int a,int b){int q=++ind;end[q]=b,next[q]=head[a],head[a]=q;}
}w,g,sav;
 
char s[N];
 
int seq[N<<1],in[N<<1],out[N<<1],tclock;
inline void dfs(int x){
    in[x]=++tclock,seq[tclock]=x;
    for(int j=g.head[x];j;j=g.next[j])dfs(g.end[j]);
    out[x]=tclock;
}
 
struct Ask{
    int lab,l,r;
    Ask(){}
    Ask(int _lab,int _l,int _r):lab(_lab),l(_l),r(_r){}
    bool operator<(const Ask&B)const{return r<B.r;}
}S[N<<1];
 
int ans[N<<1],lastins[N];
 
int A[N<<1];
inline void mdf(int x,int add){
    for(;x<=cnt;x+=x&-x)A[x]+=add;
}
inline int ask(int x){
    int r=0;for(;x;x-=x&-x)r+=A[x];return r;
}
 
int v[N<<1];
 
void dfs2(int x){
    v[x]+=v[pa[x]];
    for(int j=g.head[x];j;j=g.next[j])dfs2(g.end[j]);
}
 
void get(int x){
    for(int j=w.head[x];j;j=w.next[j])printf("%d ",w.end[j]);puts("");
}
 
int main(){
    int n,lim;scanf("%d%d",&n,&lim);
     
    register int i,j,k;int y;int p,np,q,nq,rep,tmp;
    for(root=newnode(0),i=1;i<=n;++i){
        scanf("%s",s);int l=strlen(s);
        for(last=root,j=l-1;j>=0;--j){
            if((p=tranc[last][y=s[j]-'a'])!=0){
                if(len[p]==len[last]+1)last=p;
                else{
                    rep=newnode(len[last]+1);pa[rep]=pa[p],pa[p]=rep;
                    memcpy(tranc[rep],tranc[p],sizeof tranc[p]);
                    for(tmp=last;tmp&&tranc[tmp][y]==p;tmp=pa[tmp])tranc[tmp][y]=rep;
                    last=rep;
                }
            }
            else{
                np=newnode(len[last]+1);
                for(p=last;p&&!tranc[p][y];p=pa[p])tranc[p][y]=np;
                if(!p)pa[np]=root;
                else{
                    q=tranc[p][y];
                    if(len[q]==len[p]+1)pa[np]=q;
                    else{
                        nq=newnode(len[p]+1),pa[nq]=pa[q],pa[np]=pa[q]=nq;
                        memcpy(tranc[nq],tranc[q],sizeof tranc[q]);
                        for(;p&&tranc[p][y]==q;p=pa[p])tranc[p][y]=nq;
                    }
                }last=np;
            }
            w.addedge(last,i);
            sav.addedge(i,last);
        }
    }
     
    for(i=1;i<=cnt;++i)if(pa[i])g.addedge(pa[i],i);
    dfs(1);
     
    for(i=1;i<=cnt;++i)S[i]=Ask(i,in[i],out[i]);sort(S+1,S+cnt+1);
    for(k=i=1;i<=cnt;++i){
        for(j=w.head[seq[i]];j;j=w.next[j]){
            if(lastins[w.end[j]])mdf(lastins[w.end[j]],-1);mdf(lastins[w.end[j]]=i,1);
        }
        for(;S[k].r==i;++k)ans[S[k].lab]=ask(S[k].r)-ask(S[k].l-1);
    }
     
    for(i=1;i<=cnt;++i)v[i]=ans[i]>=lim?len[i]-len[pa[i]]:0;
     
    dfs2(1);
     
    long long nowans;
    for(i=1;i<=n;++i){
        if(i>1)putchar(' ');
        for(nowans=0,j=sav.head[i];j;j=sav.next[j])nowans+=v[sav.end[j]];
        printf("%lld",nowans);
    }
     
    return 0;
}

BZOJ1396:识别子串 后缀自动机+并查集(&&BZOJ2865)

思路:

我们建立原串的后缀树,那么发现出现次数为1的子串仅能出现在叶子节点上的父亲边上.(说起来很容易)

然后发现每一个更新可以分解为两端,每一段可以相当于一条斜率恒定的线段.

因此我们将两段分开,按照截距从小到大排序,并用并查集模拟覆盖即可.

(说起来很容易)

#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
using namespace std;
 
#define N 100010
struct Node{
    Node*tranc[26],*pa;int len,begin,siz,deg;
    Node(){}
    Node(int _len,int _begin):len(_len),begin(_begin){}
}Tnull,*null=&Tnull,mem[N<<1],*P=mem,*root,*last;
Node*Newnode(int _len,int _begin){
    P->len=_len,P->begin=_begin;for(int i=0;i<26;++i)P->tranc[i]=null;P->pa=null;return P++;
}
 
char s[N];
 
struct Interval{
    int l,r,k;
    Interval(){}
    Interval(int _l,int _r,int _k):l(_l),r(_r),k(_k){}
    bool operator<(const Interval&B)const{return k<B.k;}
}S[N],S2[N];
 
int r[N];
inline void reset(int n){
    for(register int i=1;i<=n+1;++i)r[i]=i;
}
inline int find(int x){
    int q=x,rq;for(;x!=r[x];x=r[x]);for(;q!=x;q=rq)rq=r[q],r[q]=x;return x;
}
 
int v[N],vv[N];
 
Node*q[N<<1];int fr,ta;
 
int main(){
    scanf("%s",s+1);int l=strlen(s+1);
    register int i,j;int y;Node*p,*np,*q,*nq;
    for(last=root=Newnode(0,0),i=l;i>=1;--i){
        np=Newnode(last->len+1,i);np->siz=1;
        for(p=last,y=s[i]-'a';p!=null&&p->tranc[y]==null;p=p->pa)p->tranc[y]=np;
        if(p==null)np->pa=root;
        else{
            q=p->tranc[y];
            if(q->len==p->len+1)np->pa=q;
            else{
                nq=Newnode(p->len+1,0);nq->pa=q->pa,q->pa=np->pa=nq;
                memcpy(nq->tranc,q->tranc,sizeof q->tranc);
                for(;p!=null&&p->tranc[y]==q;p=p->pa)p->tranc[y]=nq;
            }
        }last=np;
    }
     
    for(p=mem;p<P;++p)++p->pa->deg;
    for(p=mem;p<P;++p)if(p->deg==0)::q[ta++]=p;
    while(fr^ta){
        p=::q[fr++];
        if(p->pa!=null){
            p->pa->siz+=p->siz;
            if(!--p->pa->deg)
                ::q[ta++]=p->pa;
        }
    }
     
    int id=0,id2=0;
    for(p=mem;p<P;++p){
        if(p->siz==1){
            //printf("%d %d %d\n",p->begin,p->len,p->pa->len);
            S[++id]=Interval(l-(p->len-p->pa->len)+1,l,1-p->begin);
            S2[++id2]=Interval(p->begin,S[id].l-1,S[id].l-p->begin+1);
        }
    }sort(S+1,S+id+1),sort(S2+1,S2+id2+1);
     
    reset(l);
    memset(v,0x3f,sizeof v);
    for(i=1;i<=id;++i){
        for(j=find(S[i].l);j<=S[i].r;j=r[j]){
            if(v[j]==0x3f3f3f3f)v[j]=S[i].k;
            r[j]=find(j+1);
        }
    }
    reset(l);
    memset(vv,0x3f,sizeof vv);
    for(i=1;i<=id2;++i){
        for(j=find(S2[i].l);j<=S2[i].r;j=r[j]){
            if(vv[j]==0x3f3f3f3f)vv[j]=S2[i].k;
            r[j]=find(j+1);
        }
    }
     
    for(i=1;i<=l;++i)printf("%d\n",min(l,min(v[i]+i,vv[i])));
     
    return 0;
}

BZOJ2780:[Spoj]8093 Sevenk Love Oimaster 后缀自动机+离线+dfs序+树状数组

思路:

首先建立多串后缀自动机(别问我怎么建的)

然后对于每个询问串在自动机上走,记录下走到的节点.那么在几个串中出现等价于逆序后缀树的子树中有几个原串的后缀.

转化为dfs序之后,这等价于每次询问一段区间有几种不同的数.

经典模型,离线+树状数组水.

(我TTMD坑了QoQ)

#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
#include<map>
using namespace std;
   
int n,m;
struct Graph{
    int head[200010],next[200010],end[200010],ind;
    inline void addedge(int a,int b){static int q=1;end[q]=b,next[q]=head[a],head[a]=q++;}
}w,g;
   
int len[200010],deg[200010],pa[200010],cnt,root,last;
map<int,int>tranc[200010];
int newnode(int l){
    len[++cnt]=l;return cnt;
}
   
char s[360010];
   
int in[200010],out[200010],seq[200010],tclock;
inline void dfs(int x){
    in[x]=++tclock;seq[tclock]=x;
    for(int j=g.head[x];j;j=g.next[j])dfs(g.end[j]);
    out[x]=tclock;
}
   
int pre[10010];
struct Ask{
    int lab,l,r;
    Ask(){}
    Ask(int _lab,int _l,int _r):lab(_lab),l(_l),r(_r){}
    bool operator<(const Ask&B)const{return r<B.r;}
}S[60010];
   
int A[200010];
inline void mdf(int x,int add){
    for(;x<=cnt;x+=x&-x)A[x]+=add;
}
inline int ask(int x){
    int r=0;for(;x;x-=x&-x)r+=A[x];return r;
}
   
int lastins[10010];
int ans[60010];
   
void get(int x){for(int j=w.head[x];j;j=w.next[j])printf("%d ",w.end[j]);puts("");}
   
int main(){
    scanf("%d%d",&n,&m);
    register int i,j,k;int l,y,p,np,q,nq,rep,tmp;
    for(root=newnode(0),i=1;i<=n;++i){
        scanf("%s",s);l=strlen(s);
        for(last=root,j=0;j<l;++j){
            if((p=tranc[last][y=s[j]])!=0){
                if(len[p]==len[last]+1)last=p;
                else{
                    rep=newnode(len[last]+1);pa[rep]=pa[p],pa[p]=rep;
                    tranc[rep]=tranc[p];
                    for(tmp=last;tmp&&tranc[tmp][y]==p;tmp=pa[tmp])tranc[tmp][y]=rep;
                    last=rep;
                }
            }
            else{
                np=newnode(len[last]+1);
                for(p=last;p&&!tranc[p][y];p=pa[p])tranc[p][y]=np;
                if(!p)pa[np]=root;
                else{
                    q=tranc[p][y];
                    if(len[q]==len[p]+1)pa[np]=q;
                    else{
                        nq=newnode(len[p]+1),pa[nq]=pa[q],pa[np]=pa[q]=nq;
                        tranc[nq]=tranc[q];
                        for(;p&&tranc[p][y]==q;p=pa[p])tranc[p][y]=nq;
                    }
                }last=np;
            }
            w.addedge(last,i);
        }
    }
    for(i=1;i<=cnt;++i)if(pa[i])g.addedge(pa[i],i);
    dfs(1);
       
    bool find;
    for(i=1;i<=m;++i){
        scanf("%s",s);l=strlen(s);find=1;
        for(p=root,j=0;j<l;++j){
            y=s[j];if(!tranc[p][y]){find=0;break;}p=tranc[p][y];
        }
        if(find)S[i]=Ask(i,in[p],out[p]);else S[i]=Ask(i,-1,-1);
    }sort(S+1,S+m+1);
       
    int noww;k=1;while(k<=m&&S[k].l==-1)++k;
    for(i=1;i<=cnt;++i){
        for(j=w.head[seq[i]];j;j=w.next[j]){
            noww=w.end[j];
            mdf(i,1);if(lastins[noww])mdf(lastins[noww],-1);lastins[noww]=i;
        }
        for(;S[k].r==i;++k)ans[S[k].lab]=ask(S[k].r)-ask(S[k].l-1);
    }
       
    for(i=1;i<=m;++i)printf("%d\n",ans[i]);
    return 0;
}

POJ3415 Common Substrings 后缀数组+单调栈

思路:

最近好像是被<囚人的旋律>里的主人公的心情所影响,所以效率很低.

例如,这样一道傻逼题我居然傻逼了3个小时!

我整个人都单调栈了.

 

就是求个height,然后在连续块内,我们对于每个属于串A的后缀分别计算出在他前面的属于串B的后缀的影响,在计算出在他之后的属于B的后缀的影响.

然后我们从前到后,再从后到前扫描一边,利用一个单调栈维护一下属于串B的后缀即可.

说起来简单...不过写起来...一定是我太鶸了.

 

#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
using namespace std;

typedef long long LL;

#define N 200010
char s1[N],s2[N],s[N];int v[N],tv[N],q[N],c[N],sa[N];
inline bool cmp(int x,int y,int hl,int n){
	return v[x]==v[y]&&((x+hl>=n&&y+hl>=n)||(x+hl<n&&y+hl<n&&v[x+hl]==v[y+hl]));
}
inline void calcsa(char*s,int n,int m,int*sa){
	register int i,j,k;int id,hl;
	for(i=0;i<m;++i)c[i]=0;
	for(i=0;i<n;++i)++c[v[i]=s[i]];
	for(i=1;i<m;++i)c[i]+=c[i-1];
	for(i=n-1;i>=0;--i)sa[--c[v[i]]]=i;
	for(int d=1;;++d){
		for(hl=1<<(d-1),id=0,i=n-hl;i<n;++i)q[id++]=i;
		for(i=0;i<n;++i)if(sa[i]>=hl)q[id++]=sa[i]-hl;
		for(i=0;i<m;++i)c[i]=0;
		for(i=0;i<n;++i)++c[v[q[i]]];
		for(i=1;i<m;++i)c[i]+=c[i-1];
		for(i=n-1;i>=0;--i)sa[--c[v[q[i]]]]=q[i];
		for(i=m=0;i<n;i=j+1,++m){
			for(j=i;j<n-1&&cmp(sa[j],sa[j+1],hl,n);++j);
			for(k=i;k<=j;++k)tv[sa[k]]=m;
		}for(i=0;i<n;++i)v[i]=tv[i];
		if(m==n)break;
	}
}
int rk[N],h[N];
inline void calch(char*s,int n,int*sa,int*rk,int*h){
	register int i,j;
	for(i=0;i<n;++i)rk[sa[i]]=i;
	for(i=0;i<n;++i)if(rk[i]){
		j=max(0,h[rk[i-1]]-1);while(i+j<n&&sa[rk[i]-1]+j<n&&s[i+j]==s[sa[rk[i]-1]+j])++j;h[rk[i]]=j;
	}
}

int lim;

struct Case{
	int v,c;
	Case(){}
	Case(int _v,int _c):v(_v),c(_c){}
}stk[N];
int top,nowcnt;long long sum[N];int cnt[N];

int belong[N];
int main(){
	int i,j,k;
	while(scanf("%d",&lim)&&lim){
		scanf("%s%s",s1,s2);int len1=strlen(s1),len2=strlen(s2);
		memset(s,0,sizeof s);int len=0;for(i=0;i<len1;++i)belong[len]=1,s[len++]=s1[i];s[len++]='$';for(i=0;i<len2;++i)belong[len]=2,s[len++]=s2[i];
		calcsa(s,len,256,sa),calch(s,len,sa,rk,h);

		LL ans=0;int tmp;
		for(i=1;i<len;){
			if(h[i]>=lim){
				for(j=i;j<len&&h[j]>=lim;++j);
				for(top=0,h[i-1]=h[i],tmp=h[j],h[j]=h[j-1],k=i-1;k<j;++k){
					nowcnt=0;
					while(top&&stk[top].v>=h[k])nowcnt+=stk[top--].c;
					if(nowcnt)stk[++top]=Case(h[k],nowcnt),sum[top]=sum[top-1]+(LL)stk[top].c*stk[top].v,cnt[top]=cnt[top-1]+stk[top].c;
					if(belong[sa[k]]==2)ans+=sum[top]-(LL)cnt[top]*(lim-1);
					else{
						nowcnt=1;while(top&&stk[top].v>=h[k+1])nowcnt+=stk[top--].c;
						stk[++top]=Case(h[k+1],nowcnt),sum[top]=sum[top-1]+(LL)stk[top].c*stk[top].v,cnt[top]=cnt[top-1]+stk[top].c;
					}
					
				}
				for(top=0,k=j-1;k>=i-1;--k){
					nowcnt=0;
					while(top&&stk[top].v>=h[k+1])nowcnt+=stk[top--].c;
					if(nowcnt)stk[++top]=Case(h[k+1],nowcnt),sum[top]=sum[top-1]+(LL)stk[top].c*stk[top].v,cnt[top]=cnt[top-1]+stk[top].c;
					if(belong[sa[k]]==2)ans+=sum[top]-(LL)cnt[top]*(lim-1);
					else{
						nowcnt=1;while(top&&stk[top].v>=h[k])nowcnt+=stk[top--].c;
						stk[++top]=Case(h[k],nowcnt),sum[top]=sum[top-1]+(LL)stk[top].c*stk[top].v,cnt[top]=cnt[top-1]+stk[top].c;
					}
				}h[j]=tmp;
				i=j;
			}else++i;
		}
		cout<<ans<<endl;
	}
	return 0;
}

POJ3693 Maximum repetition substring 后缀数组

思路:

这个题简直丧心病狂.终于是过了.

注意到重复出现一次的字串必定是字典序最小的字符.我们只考虑重复出现不少于两次的子串.

首先我们枚举重复出现的每一小段的长度\(l\).考虑若干个端点\(0,l,2l,...,i*l,(i+1)*l\),若存在每一小段长度为\(l\)且连续重复出现至少两次的子串,则至少覆盖两个端点.

因此我们枚举相邻的两个端点,分别找出向前最多能匹配多少,向后最多能匹配多少,我们就找到了两个完全相等的段,而且其错开了\(l\)的长度.不妨令其长度为\(d\),则(通过画图)此时的最大重复次数为\(\frac{d}{l}+1\).

如果不要求字典序的话,我们直接这样找到答案就行了.用后缀数组预处理一大堆东西能做到各种询问\(O(1)\)时间复杂度为:

\[\frac{n}{1}+\frac{n}{2}+...+\frac{n}{n}=O(nlogn)\]

因此总的时间复杂度为\(O(nlogn)\).

关键是要求字典序最小.

我们发现每次找到两个完全相等的段时都存在很多合法的答案,也就是若干长度相等,但起点位置不同的子串.我们预处理出起点区间内字典序最小的子串,并利用这个更新答案即可.

不管怎么样,反正时间复杂度\(O(nlogn)\).

#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
using namespace std;

#define N 100010
int len;
char s[N],ss[N];

int v[N],tmpv[N],cnt[N],q[N],sa1[N],sa2[N];
inline bool cmp(int x,int y,int hl,int n){
	return v[x]==v[y]&&((x+hl>=n&&y+hl>=n)||(x+hl<n&&y+hl<n&&v[x+hl]==v[y+hl]));
}
inline void calcsa(char*s,int n,int m,int*sa){
	register int i,j,k;int hl,id;
	for(i=0;i<m;++i)cnt[i]=0;
	for(i=0;i<n;++i)++cnt[v[i]=s[i]];
	for(i=1;i<m;++i)cnt[i]+=cnt[i-1];
	for(i=n-1;i>=0;--i)sa[--cnt[v[i]]]=i;
	for(int d=1;;++d){
		for(hl=1<<(d-1),id=0,i=n-hl;i<n;++i)q[id++]=i;
		for(i=0;i<n;++i)if(sa[i]>=hl)q[id++]=sa[i]-hl;
		for(i=0;i<m;++i)cnt[i]=0;
		for(i=0;i<n;++i)++cnt[v[q[i]]];
		for(i=1;i<m;++i)cnt[i]+=cnt[i-1];
		for(i=n-1;i>=0;--i)sa[--cnt[v[q[i]]]]=q[i];
		for(m=i=0;i<n;m++,i=j+1){
			for(j=i;j<n-1&&cmp(sa[j],sa[j+1],hl,n);++j);
			for(k=i;k<=j;++k)tmpv[sa[k]]=m;
		}for(i=0;i<n;++i)v[i]=tmpv[i];
		if(m==n)break;
	}
}
int rk1[N],rk2[N],h1[N],h2[N];
inline void calch(char*s,int n,int*sa,int*rk,int*h){
	register int i,j;
	for(i=0;i<n;++i)rk[sa[i]]=i;
	for(i=0;i<n;++i)if(rk[i]){
		j=max(0,h[rk[i-1]]-1);while(i+j<n&&sa[rk[i]-1]+j<n&&s[i+j]==s[sa[rk[i]-1]+j])++j;h[rk[i]]=j;
	}
}
int pre[N],rh1[N][21],rh2[N][21];
inline void Initlen(int n){
	for(register int i=2;i<=n;++i)pre[i]=pre[i>>1]+1;
}
inline void Initrmq(int*source,int rm[][21],int n){
	register int i,j;
	for(i=0;i<n;++i)rm[i][0]=source[i];
	for(j=1;j<=20;++j)for(i=0;i+(1<<j)-1<n;++i)rm[i][j]=min(rm[i][j-1],rm[i+(1<<(j-1))][j-1]);
}
inline int askmin(int rm[][21],int l,int r){
	return min(rm[l][pre[r-l+1]],rm[r-(1<<pre[r-l+1])+1][pre[r-l+1]]);
}
inline int getlcp(int x,int y){
	static int rx,ry;rx=rk1[x],ry=rk1[y];if(rx>ry)swap(rx,ry);
	return x==y?len-x:askmin(rh1,rx+1,ry);
}
inline int getlcs(int x,int y){
	static int rx,ry;rx=rk2[len-1-x],ry=rk2[len-1-y];if(rx>ry)swap(rx,ry);
	return x==y?x+1:askmin(rh2,rx+1,ry);
}
int mins[N][21];
inline void Initmins(){
	register int i,j;
	for(i=0;i<len;++i)mins[i][0]=i;
	for(j=1;j<=20;++j)for(i=0;i+(1<<j)-1<len;++i)mins[i][j]=rk1[mins[i][j-1]]<rk1[mins[i+(1<<(j-1))][j-1]]?mins[i][j-1]:mins[i+(1<<(j-1))][j-1];
}
inline int getmins(int l,int r){
	int sl=mins[l][pre[r-l+1]],sr=mins[r-(1<<pre[r-l+1])+1][pre[r-l+1]];
	return rk1[sl]<rk1[sr]?sl:sr;
}

struct Ans{
	int ins,len,t;
	Ans(){}
	Ans(int _ins,int _len,int _t):ins(_ins),len(_len),t(_t){}
	bool operator==(const Ans&B)const{return ins==B.ins&&len==B.len&&t==B.t;}
	bool operator<(const Ans&B)const{
		if(t!=B.t)return t>B.t;
		if(ins==B.ins)return len<B.len;
		int rx=rk1[ins],ry=rk1[B.ins],lcp=getlcp(ins,B.ins);
		if(len>lcp&&B.len>lcp)return rx<ry;else return len<B.len;
	}
}res;
inline void upd(Ans&x,Ans y){if(y<x)x=y;}

int main(){
	//freopen("tt.in","r",stdin);
	int test=0;
	register int i,j;
	while(scanf("%s",s)&&s[0]!='#'){
		printf("Case %d: ",++test); 
		len=strlen(s);for(i=0;i<len;++i)ss[i]=s[len-1-i];
		calcsa(s,len,256,sa1),calcsa(ss,len,256,sa2);
		calch(s,len,sa1,rk1,h1),calch(ss,len,sa2,rk2,h2);
		Initlen(len);
		Initrmq(h1,rh1,len),Initrmq(h2,rh2,len);
		Initmins();
		res=Ans(0,0,0);
		int lcp,lcs,d;
		for(int l=1;l<=len/2;++l){
			for(i=0;(i+1)*l<len;++i){
				lcp=getlcp(i*l,(i+1)*l);
				lcs=getlcs(i*l,(i+1)*l);
				d=lcp+lcs-1;
				upd(res,Ans(getmins(i*l-lcs+1,(i+1)*l+lcp-(d/l+1)*l),(d/l+1)*l,d/l+1));
			}
		}
		if(res.t==1){
			int c=~0U>>1;for(i=0;i<len;++i)c=min(c,(int)s[i]);printf("%c\n",c);
		}
		else{for(j=0;j<res.len;++j)putchar(s[res.ins+j]);puts("");}	
	}
	return 0;
}