zoukankan      html  css  js  c++  java
  • SAM 后缀自动机

    bzoj3676 回文串

    题目大意:给定一个字符串,求其中某种回文串的长度*出现次数的最大值。

    思路:建立后缀自动机,用manachur求出本质不同的回文串(也就是比较使pp[i]+1的时候),然后在后缀自动机上的相应节点往上找fa,统计siz。

    (这道题目中manacher不能加字符(会mle),所以要奇偶分别匹配,但是偶数的时候是有问题的。)

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define N 600005
    #define maxsiz 26
    #define up 20
    #define LL long long
    using namespace std;
    char ss[N];
    int sz,last,ll,pp[N]={0};
    LL ans=0LL;
    struct use{
        int ch[N][maxsiz],fa[N],mx[N],siz[N],que[N],id[N],anc[N][up];
        int idx(char ch){return ch-'a';}
        void extend(char x,int ii){
            int i,j,u,p,q,np,nq;
            u=idx(x);p=last;id[ii]=np=++sz;
            mx[np]=mx[p]+1;siz[np]=1;
            while(p && !ch[p][u]){ch[p][u]=np;p=fa[p];}
            if (!p) fa[np]=1;
            else{
                q=ch[p][u];
                if (mx[p]+1==mx[q]) fa[np]=q;
                else{
                    nq=++sz;mx[nq]=mx[p]+1;
                    memcpy(ch[nq],ch[q],sizeof(ch[q]));
                    fa[nq]=fa[q];fa[q]=fa[np]=nq;
                    while(ch[p][u]==q){ch[p][u]=nq;p=fa[p];}
                }
            }last=np;}
        void pre(){
            int i,j;
            memset(pp,0,sizeof(pp));
            for (i=1;i<=sz;++i) ++pp[mx[i]];
            for (i=1;i<=ll;++i) pp[i]+=pp[i-1];
            for (i=sz;i;--i) que[pp[mx[i]]--]=i;
            for (i=sz;i;--i) siz[fa[que[i]]]+=siz[que[i]];
            memset(anc[0],0,sizeof(anc[0]));
            for (i=1;i<=sz;++i){
                anc[que[i]][0]=fa[que[i]];
                for (j=1;j<up;++j) anc[que[i]][j]=anc[anc[que[i]][j-1]][j-1];
            }}
        void query(int l,int r){
            int u,v,i,j;u=id[r];
            for (i=up-1;i>=0;--i)
              if (mx[anc[u][i]]>=r-l+1) u=anc[u][i];
            ans=max(ans,(LL)siz[u]*(LL)(r-l+1));
        }
    }sam;
    void work(){
        int i,j,mx,id;
        memset(pp,0,sizeof(pp));
        for (mx=0,i=1;i<ll;++i){
            if (mx>i) pp[i]=min(pp[2*id-i],mx-i);
            else{pp[i]=1;sam.query(i,i);}
            for (;ss[i-pp[i]]==ss[i+pp[i]];++pp[i])
                sam.query(i-pp[i],i+pp[i]);
            if (pp[i]+i>mx){mx=pp[i]+i;id=i;}
        }memset(pp,0,sizeof(pp));
        for (mx=0,i=1;i<ll;++i){
            if (mx>i) pp[i]=min(pp[2*id-i-1],mx-i);
            else pp[i]=0;
            for (;ss[i-pp[i]]==ss[i+pp[i]+1];++pp[i])
                sam.query(i-pp[i],i+pp[i]+1);
            if (pp[i]+i>mx){mx=pp[i]+i;id=i;}
        }
    }
    int main(){
        int i,j,n;
        scanf("%s",ss+1);ss[0]='&';
        ll=strlen(ss+1);ss[++ll]='#';
        for (sz=last=1,i=1;i<ll;++i) sam.extend(ss[i],i);
        sam.pre();work();printf("%I64d
    ",ans);
    }
    View Code

    bzoj3926 诸神眷顾的幻想乡

    题目大意:给定一棵树和每个节点的颜色,求颜色序列不同的链的个数(AB和BA是不同的)。

    思路:因为题目保证了每个节点最多有20个相邻节点,所以可以从每个叶子dfs,然后加点,这里有一个小优化:如果这个节点和父亲的关系没变就可以不用新开节点。对于每个叶子节点出去的串都是从根开始建的,最后答案就是sigma mx[i]-mx[fa[i]]。

    注意:因为一个点会加入多次数组要开的足够大

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define N 200005
    #define M 2000005
    #define ms 10
    #define LL long long
    using namespace std;
    int point[N]={0},next[N],en[N],co[N],tot=0,c,du[N],sz,cnt[M]={0},id[N];
    struct use{
        int ch[M][ms],fa[M],mx[M],siz[M],que[M];
        int extend(int pp,int u){
            int i,j,p,np,q,nq;np=ch[p=pp][u];
            if (np && mx[np]==mx[pp]+1) return np;
            np=++sz;mx[np]=mx[p]+1;siz[np]=1;
            while(p && !ch[p][u]){ch[p][u]=np;p=fa[p];}
            if (!p) fa[np]=1;
            else{
                q=ch[p][u];
                if (mx[q]==mx[p]+1) fa[np]=q;
                else{
                    nq=++sz;mx[nq]=mx[p]+1;
                    memcpy(ch[nq],ch[q],sizeof(ch[q]));
                    fa[nq]=fa[q];fa[q]=fa[np]=nq;
                    while(ch[p][u]==q){ch[p][u]=nq;p=fa[p];}
                }
            }return np;}
        LL calc(){
            int i,j;LL ans=0;
            for (i=2;i<=sz;++i) ans+=(LL)(mx[i]-mx[fa[i]]);
            return ans;} 
    }sam;
    void add(int u,int v){
        next[++tot]=point[u];point[u]=tot;en[tot]=v;
        next[++tot]=point[v];point[v]=tot;en[tot]=u;
        ++du[u];++du[v];}
    void dfs(int u,int ff,int len){
        int i,j,v;id[u]=sam.extend(id[ff],co[u]);
        for (i=point[u];i;i=next[i]){
            if ((v=en[i])==ff) continue;
            dfs(v,u,len+1);
        }
    }
    int main(){
        int i,j,u,v,n,m;scanf("%d%d",&n,&c);
        for (i=1;i<=n;++i) scanf("%d",&co[i]);
        for (i=1;i<n;++i){scanf("%d%d",&u,&v);add(u,v);}
        sz=id[0]=1;
        for (i=1;i<=n;++i)
          if (du[i]==1) dfs(i,0,1);
        printf("%I64d
    ",sam.calc());
    }
    View Code

    bzoj3998 弦论

    题目大意:求给定字符串第k小的字串(T=0的时候,不同位置的相同子串算一个;T=1的时候,不同位置的相同子串算多个)。

    思路:建后缀自动机,求right集合大小(siz),更新出每个点后面有多少个子串(两种),T=0的是1、T=1的是siz[i],1号点的都是0。然后每位每位的求就可以了。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define N 1000005
    #define M 5000000
    #define mz 26
    #define LL long long
    using namespace std;
    int last,sz,pp[N]={0},l,point[N],next[M],en[M],du[N]={0},tot=0;
    char ss[N];
    void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;++du[v];}
    struct use{
        int ch[N][mz],fa[N],mx[N],que[N];
        LL siz[N],fi[2][N];
        int idx(char x){return x-'a';}
        void extend(char x){
            int u,p,q,np,nq;
            u=idx(x);p=last;np=++sz;
            mx[np]=mx[p]+1;siz[np]=1LL;
            while(p && !ch[p][u]){ch[p][u]=np;p=fa[p];}
            if (!p) fa[np]=1;
            else{
                q=ch[p][u];
                if (mx[p]+1==mx[q]) fa[np]=q;
                else{
                    nq=++sz;mx[nq]=mx[p]+1;
                    memcpy(ch[nq],ch[q],sizeof(ch[q]));
                    fa[nq]=fa[q];fa[q]=fa[np]=nq;
                    while(ch[p][u]==q){ch[p][u]=nq;p=fa[p];}
                }
            }last=np;}
        void pre(){
            int i,j;
            for (i=1;i<=sz;++i){
                ++pp[mx[i]];
                for (j=0;j<mz;++j)
                    if (ch[i][j]) add(ch[i][j],i);
            }for (i=1;i<=l;++i) pp[i]+=pp[i-1];
            for (i=sz;i;--i) que[pp[mx[i]]--]=i;
            for (i=sz;i;--i) siz[fa[que[i]]]+=siz[que[i]];
        }
        void topu(){
            int i,u,v,head=0,tail=0;
            for (i=1;i<=sz;++i){
              if (!du[i]) que[++tail]=i;
              fi[0][i]=(i==1 ? 0LL : 1LL);
              fi[1][i]=(i==1 ? 0LL : siz[i]);
            }while(head<tail){
                u=que[++head];
                for (i=point[u];i;i=next[i]){
                    if (!(--du[v=en[i]])) que[++tail]=v;
                    fi[0][v]+=fi[0][u];fi[1][v]+=fi[1][u];
                }
            }
        }
        int work(int t,LL k){
            int i,u,v;l=0;
            if (k>fi[t][u=1]) return 0;
            while(u){
                if (k<=0) return 1;
                for (i=0;i<mz;++i){
                    if (!(v=ch[u][i])) continue;
                    if (k>fi[t][v]) k-=fi[t][v];
                    else{
                        ss[l++]=i+'a';u=v;
                        k-=(!t ? 1 : siz[u]);
                        break;}
                }if (i>=mz) break;
            }return 1;}
    }sam;
    int main(){
        int i,t;LL k;scanf("%s",ss);l=strlen(ss);
        for (i=0,sz=last=1;i<l;++i) sam.extend(ss[i]);
        sam.pre();sam.topu();
        scanf("%d%I64d",&t,&k);
        if (!sam.work(t,k)) printf("-1
    ");
        else{
            for (i=0;i<l;++i) putchar(ss[i]);
            printf("
    ");}
    }
    View Code

    bzoj4032 最短不公共子串

    题目大意:求a的最短的子串/子序列,不是b的子串/子序列。

    思路:1)a的子串、b的子串,fi[i][j]表示a到i、b到j的最长公共子串,答案是i和所有j的最大值的最小值+1;2)a的子串、b的子序列,fi[i][j]表示a到i、b到j的最长公共子串,更新的时候略有不同,可以由x=0~j-1的fi[i-1][x]更新fi[i][j],最后答案还是最大值的最小值+1;3)a的子序列、b的子串,对b建后缀自动机,在后缀自动机上dp,ff[i]表示匹配到i这个节点和a的最大匹配长度,答案是ff[i]的最小值+1;4)a的子序列、b的子序列,ff[i]表示b到i和a的最大匹配长度,类似背包优化,第二维倒着循环,记录b中每位之后每个字母的最前出现位置,然后更新,答案是ff[i]的最小值+1。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define N 4005
    #define mz 26
    using namespace std;
    int la,lb,fi[N][N],last,sz,gi[N][mz],cc[mz],ff[N];
    char a[N],b[N];
    int calc1(){
        int i,j,ci,mn;mn=N;
        memset(fi,0,sizeof(fi));
        for (i=1;i<=la;++i){
            for (ci=0,j=1;j<=lb;++j){
                if (a[i]==b[j]) fi[i][j]=fi[i-1][j-1]+1;
                ci=max(ci,fi[i][j]);
            }if (ci!=i) mn=min(mn,ci);
        }return (mn==N ? -1 : mn+1);
    }
    int calc2(){
        int i,j,bi,ci,mn;mn=N;
        memset(fi,0,sizeof(fi));
        for (i=1;i<=la;++i){
            for (bi=ci=0,j=1;j<=lb;++j){
                if (a[i]==b[j]) fi[i][j]=bi+1;
                ci=max(ci,fi[i][j]);
                bi=max(bi,fi[i-1][j]);
            }if (ci!=i) mn=min(mn,ci);
        }return (mn==N ? -1 : mn+1);
    }
    struct use{
        int fa[N],ch[N][mz],mx[N],po[N];
        int idx(char ch){return ch-'a';}
        void extend(char c,int x){
            int u,p,q,np,nq;
            u=idx(c);p=last;np=++sz;
            mx[np]=mx[p]+1;
            while(p && !ch[p][u]){ch[p][u]=np;p=fa[p];}
            if (!p) fa[np]=1;
            else{
                q=ch[p][u];
                if (mx[q]==mx[p]+1) fa[np]=q;
                else{
                    nq=++sz;mx[nq]=mx[p]+1;
                    memcpy(ch[nq],ch[q],sizeof(ch[q]));
                    fa[nq]=fa[q];fa[q]=fa[np]=nq;
                    while(ch[p][u]==q){ch[p][u]=nq;p=fa[p];}
                }
            }last=np;po[x]=np;
        }
        int calc3(){
            int i,j,k,mn,ci;mn=N;last=sz=1;
            for (i=1;i<=lb;++i) extend(b[i],i);
            memset(ff,60,sizeof(ff));ff[1]=0;
            for (i=1;i<=la;++i)
              for (j=1;j<=sz;++j){
                  if (!(ci=ch[j][idx(a[i])])) mn=min(mn,ff[j]);
                  else ff[ci]=min(ff[ci],ff[j]+1);
              }
            return (mn==N ? -1 : mn+1);
        }
    }sam;
    int calc4(){
        int i,j,mn,ci;mn=N;
        for (i=0;i<mz;++i) cc[i]=lb+1;
        for (i=lb;i>=0;--i){
            for (j=0;j<mz;++j) gi[i][j]=cc[j];
            cc[b[i]-'a']=i;
        }memset(ff,60,sizeof(ff));ff[0]=0;
        for (i=1;i<=la;++i)
            for (j=lb;j>=0;--j){
                if ((ci=gi[j][a[i]-'a'])>lb) mn=min(mn,ff[j]);
                else ff[ci]=min(ff[ci],ff[j]+1);
            }
        return (mn==N ? -1 : mn+1);
    }
    int main(){
        scanf("%s%s",a+1,b+1);la=strlen(a+1);lb=strlen(b+1);
        printf("%d
    %d
    %d
    %d
    ",calc1(),calc2(),sam.calc3(),calc4());
    }
    View Code

    bzoj4180 字符串计数

    题目大意:一个字符串S只由ABCD组成,每次从S中找一个字串加入T,求一个长度为n的最小操作次数的最大值。

    思路:考虑二分操作次数,然后判断当前操作次数下最小的字符串长度。fi[i][j]表示i开头j结尾的最短长度(因为考虑转移,所以表示成i开头,j结尾就不是字串的最短长度比较好做),这个数组可以用后缀自动机拓扑一边求出来,但因为是i开头j结尾不是字串的长度,所以要从rt开始的串才行。对于判断,矩乘,然后看能否有一个i开头j结尾的长度小于n。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define N 500000
    #define LL long long
    #define inf 0x7fffffffffffffffLL
    using namespace std;
    char ss[N];
    LL n;
    int last,sz;
    char in(){
        char ch=getchar();
        while(ch<'A'||ch>'Z') ch=getchar();
        return ch;}
    struct mat{
        LL num[4][4];
        void init(){
            memset(num,0,sizeof(num));
            for (int i=0;i<4;++i) num[i][i]=1LL;}
        mat operator*(const mat &x)const{
            int i,j,k;mat c;
            for (i=0;i<4;++i)
              for (j=0;j<4;++j){
                  c.num[i][j]=inf;
                  for (k=0;k<4;++k) c.num[i][j]=min(c.num[i][j],num[i][k]+x.num[k][j]);
              }return c;}
    }ji;
    mat mi(mat x,LL y){
        mat a;a.init();
        for (;y;y/=2LL){
            if (y%2LL) a=a*x;
            x=x*x;
        }return a;}
    struct use{
        int fa[N],ch[N][4],mx[N];LL mn[N][4];
        bool vi[N];
        void init(){
            memset(fa,0,sizeof(fa));
            memset(ch,0,sizeof(ch));
            memset(mx,0,sizeof(mx));
            memset(vi,false,sizeof(vi));}
        int idx(char u){return u-'A';}
        void extend(char c){
            int u,p,q,np,nq;u=idx(c);
            p=last;np=++sz;mx[np]=mx[p]+1;
            while(p && !ch[p][u]){ch[p][u]=np;p=fa[p];}
            if (!p) fa[np]=1;
            else{
                q=ch[p][u];
                if (mx[q]==mx[p]+1) fa[np]=q;
                else{
                    nq=++sz;mx[nq]=mx[p]+1;
                    memcpy(ch[nq],ch[q],sizeof(ch[q]));
                    fa[nq]=fa[q];fa[q]=fa[np]=nq;
                    while(ch[p][u]==q){ch[p][u]=nq;p=fa[p];}
                }
            }last=np;}
        void dfs(int u){
            if (vi[u]) return;vi[u]=true;
            int i,j;
            for (i=0;i<4;++i){
                if (ch[u][i]) dfs(ch[u][i]);
                mn[u][i]=inf/3LL;
            }for (i=0;i<4;++i){
              if (!ch[u][i]){mn[u][i]=1LL;continue;}
              for (j=0;j<4;++j) mn[u][j]=min(mn[u][j],mn[ch[u][i]][j]+1LL);
            }
        }
    }sam;
    bool judge(LL x){
        int i,j;
        mat ci=mi(ji,x);
        for (i=0;i<4;++i)
          for (j=0;j<4;++j)
              if (ci.num[i][j]+1LL<=n) return true;
        return false;}
    int main(){
        int i,j,ll;LL l,r,mid,ans;scanf("%I64d",&n);
        scanf("%s",ss);ll=strlen(ss);sam.init();
        for (last=sz=1,i=0;i<ll;++i) sam.extend(ss[i]);
        sam.dfs(1);l=0;r=n+1;
        for (i=0;i<4;++i)
          for (j=0;j<4;++j) ji.num[i][j]=sam.mn[sam.ch[1][i]][j];
        while(l<=r){
            mid=(l+r)/2LL;
            if (judge(mid)){l=mid+1;ans=mid;}
            else r=mid-1;
        }printf("%I64d
    ",ans+1);
    }
    View Code

    bzoj4545 DQS的trie

    题目大意:维护一个trie,询问trie上本质不同的子串个数和某种串出现的个数。

    思路:建后缀自动机,用lct维护子树的大小的方法是在链上放标记(维护一些点的权值表示子树大小)。全局不同子串的个数用全局变量=mx[x]-mx[fa[x]]就可以了;某个串出现次数是从后缀自动机上顺着走,最后一个点的子树大小就是答案。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define N 200005
    #define up 3
    #define LL long long
    using namespace std;
    int in(){
        char ch=getchar();int x=0;
        while(ch<'0'||ch>'9') ch=getchar();
        while(ch>='0'&&ch<='9'){
            x=x*10+ch-'0';ch=getchar();
        }return x;}
    int chin(){
        char ch=getchar();
        while(ch<'a'||ch>'z') ch=getchar();
        return ch-'a';}
    int point[N],next[N],en[N],va[N],tot=0,ff[N]={0},id[N]={0},sz,co[N];
    char ss[N];
    LL ans=0LL;
    void add(int u,int v,int c){
        next[++tot]=point[u];point[u]=tot;en[tot]=v;va[tot]=c;
        next[++tot]=point[v];point[v]=tot;en[tot]=u;va[tot]=c;}
    struct lct{
        int fa[N],ch[N][2],sz[N],del[N],que[N];
        bool isroot(int x){return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;}
        bool isd(int x){return ch[fa[x]][1]==x;}
        void paint(int x,int y){if (x){del[x]+=y;sz[x]+=y;}}
        void pushdown(int x){
            int l,r;l=ch[x][0];r=ch[x][1];
            if (del[x]){paint(l,del[x]);paint(r,del[x]);del[x]=0;}
        }
        void rotate(int x){
            int y,z,l,r;z=fa[y=fa[x]];
            if (ch[y][0]==x) l=0;
            else l=1; r=l^1;
            if (!isroot(y)){
                if (y==ch[z][0]) ch[z][0]=x;
                else ch[z][1]=x;
            }fa[x]=z;fa[y]=x;fa[ch[x][r]]=y;
            ch[y][l]=ch[x][r];ch[x][r]=y;
        }
        void splay(int x){
            int i,y,z,qz;que[qz=1]=x;
            for (i=x;!isroot(i);i=fa[i]) que[++qz]=fa[i];
            for (;qz;--qz){
                pushdown(que[qz]);
            }while(!isroot(x)){
                z=fa[y=fa[x]];
                if (!isroot(y)){
                    if (isd(y)==isd(x)) rotate(y);
                    else rotate(x);
                }rotate(x);
            }
        }
        void access(int x){int y=0;while(x){splay(x);ch[x][1]=y;y=x;x=fa[x];}}
        void link(int x,int y){fa[x]=y;access(y);splay(y);paint(y,sz[x]);}
        void cut(int x){
            access(x);splay(x);paint(ch[x][0],-sz[x]);
            fa[ch[x][0]]=0;ch[x][0]=0;
        }
    }tr;
    struct use{
        int fa[N],ch[N][up],mx[N];
        int extend(int pp,int u){
            int i,j,p,np,q,nq;p=pp;
            mx[np=++sz]=mx[p]+1;tr.sz[np]=1;
            while(p&&!ch[p][u]){ch[p][u]=np;p=fa[p];}
            if (!p){fa[np]=1;tr.link(np,1);ans+=(LL)(mx[np]-mx[1]);}
            else{
                q=ch[p][u];
                if (mx[q]==mx[p]+1){fa[np]=q;ans+=(LL)(mx[np]-mx[q]);tr.link(np,q);}
                else{
                    nq=++sz;mx[nq]=mx[p]+1;
                    memcpy(ch[nq],ch[q],sizeof(ch[q]));
                    fa[nq]=fa[q];tr.link(nq,fa[q]);
                    ans+=(LL)(mx[nq]-mx[fa[nq]]);
                    ans-=(LL)(mx[q]-mx[fa[q]]);
                    tr.cut(q);fa[q]=fa[np]=nq;
                    ans+=(LL)(mx[q]-mx[nq]+mx[np]-mx[nq]);
                    tr.link(q,nq);tr.link(np,nq);
                    while(ch[p][u]==q){ch[p][u]=nq;p=fa[p];}
                }
            }return np;}
        int query(){
            int i,l,u=1;l=strlen(ss);
            for (i=0;i<l;++i){
                if (!ch[u][ss[i]-'a']) return 0;
                u=ch[u][ss[i]-'a'];
            }tr.splay(u);return tr.sz[u];
        }
    }sam;
    void dfs(int u,int f,int k){
        int i,v;ff[u]=f;
        if (k){
            id[u]=sam.extend(id[f],co[u]);
        }for (i=point[u];i;i=next[i]){
            if ((v=en[i])==f||ff[v]) continue;
            co[v]=va[i];dfs(v,u,k+1);
        }
    }
    int main(){
        int n,i,j,m,u,v,c,op,rt,si;
        in();n=in();
        for (i=1;i<n;++i){
            u=in();v=in();c=chin();
            add(u,v,c);
        }sz=id[1]=1;dfs(1,0,0);m=in();
        while(m--){
            op=in();
            if (op==1) printf("%I64d
    ",ans);
            if (op==2){
                rt=in();si=in();
                for (i=1;i<si;++i){
                    u=in();v=in();c=chin();
                    add(u,v,c);
                }dfs(rt,ff[rt],0);
            }if (op==3){
                scanf("%s",ss);
                printf("%d
    ",sam.query());
            }
        }
    }
    View Code
  • 相关阅读:
    NVIDIA-SMI has failed because it couldn't communicate with the NVIDIA driver. Make sure that the latest NVIDIA driver is installed and running.
    QOpenGLShaderProgram: could not create shader program
    ubuntu loading initial ramdisk 卡住
    ubuntu 下pip install sasl报错fatal error: sasl/sasl.h: No such file or directory
    ImportError: No module named managers
    python docker 多进程提供 稳定tensorflow gpu 线上服务
    侧脸生成正脸概论与精析(一)Global and Local Perception GAN
    pytorch安装 caffe2 安装:git 慢 caffe2 cannot find -lopencv_dep_cudart ,undefined reference to 'pthread_create'
    undefined symbol: PyFPE_jbuf
    Metasploit后渗透模块开发
  • 原文地址:https://www.cnblogs.com/Rivendell/p/5154023.html
Copyright © 2011-2022 走看看