[Usaco2007 Dec]队列变换 贪心+SA

    首先,这是一道贪心题。

    先来看一个简易版本的题目: 给你两个字符串S1 S2,答案串最开始是空的, 每次都可以从两个字符串开头拿一个字符到答案串末尾。直到两个字符串全部取完。 现在要求一个操作序列。 使得答案串的字典序最小。  

    这显然是一个贪心的过程。 如果s1串的首字符小于s2串首字符,则取s1的首字符,反之,则取s2串的首字符。。。 到此为止的贪心都是很显然的, 问题在于当s1首字符和s2首字符相同时又该如何选取呢。。 这是不能随便选的(废话)。 这时候一个很显然的想法就是比较当前s1串和当前s2串的字典序大小。 若s1小于s2,则取s1首字符,反之,则取s2首字符。  那两个字典序一样大呢? 随便取就行了(因为这时候s1和s2是相同的

    这道题无非就是s2是s1的逆序串。。 想清楚策略之后就是SA裸题了 

    

#include <iostream>
#include <cstdio>
#define ws wss
#define rank rankk
#define MAXN 100000
using namespace std;
int n,m;
char ans[MAXN];
char c[MAXN];
int t1[MAXN],t2[MAXN],r[MAXN],sa[MAXN],rank[MAXN],ws[MAXN];
int cmp(int *r,int a,int b,int l)
{
    return r[a]==r[b]&&r[a+l]==r[b+l];
}
void da(int *r,int n,int m)
{
    int *x=t1,*y=t2,i,j,p;
    for (i=0;i<m;++i) ws[i]=0;
    for (i=0;i<n;++i) ws[x[i]=r[i]]++;
    for (i=1;i<m;++i) ws[i]+=ws[i-1];
    for (i=n-1;i>=0;--i) sa[--ws[x[i]]]=i;
    for (j=1,p=1;p<n;j*=2,m=p)
    {
        for (i=n-j,p=0;i<n;++i) y[p++]=i;
        for (i=0;i<n;++i) if (sa[i]>=j) y[p++]=sa[i]-j;
        for (i=0;i<m;++i) ws[i]=0;
        for (i=0;i<n;++i) ws[x[y[i]]]++;
        for (i=1;i<m;++i) ws[i]+=ws[i-1];
        for (i=n-1;i>=0;--i) sa[--ws[x[y[i]]]]=y[i];
        swap(x,y);
        x[sa[0]]=0;
        for (i=1,p=1;i<n;++i)
            x[sa[i]]=cmp(y,sa[i],sa[i-1],j)?p-1:p++;
    }
}
inline int f(int x)
{
    return rank[x];
}
inline int g(int x)
{
    x=2*m-x;
    return rank[x];
}
int main()
{
    cin>>n;
    m=n;
    for (int i=0;i<n;++i)
    {
        while (isspace(c[i]=getchar())) ;
        r[i]=(int)c[i];
    }
    r[n]=1;
    for (int i=n+1;i<=2*n;++i) r[i]=r[2*n-i];
    r[2*n+1]=0;
    n=2*n+2;
    da(r,n,200);
    for (int i=1;i<n;++i) rank[sa[i]]=i;
    int l=0,r=m-1;
    for (int i=1;i<=m;++i)
    {
        if (f(l)<g(r)) {ans[i]=c[l];++l;}
        else {ans[i]=c[r];--r;}
    }
    for (int i=1;i<=m;++i)
    {
        putchar(ans[i]);
        if (i%80==0) putchar('\n');
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值