2013 多校第九场 hdu 4691 Front compression(暴力 + 剪枝 OR 后缀数组)

题目:http://acm.hdu.edu.cn/showproblem.php?pid=4691

题目大意:输入一组字符串,对于输入的每一个字符串,输出一个数字加一个空格加一个字符串加一个回车,数字为它和上一个输入的字符串的公共前缀的长度,字符串为未匹配的字符串,空串的话长度也为1,最后输出两个数,分别是输入的所有的长度和和输出的长度和,空格和回车都算一个。

思路:暴力枚举前缀,然后TLE,然后加了一个剪枝,就是:如果前一个字符串的起始位置和现在这个一样,那么就直接算出来。然后就这样神奇的 3000ms 飘过了。。 只能说测试数据里这样的数据太多了。。 = =

这题的正解应该是后缀数组,今天花了一天去学了这个东西。 现在发现这题应该是后缀数组里很基础的了,前后两者的重叠部分的长度就是 min( lcp(pre_a,a), b - a ,pre_b - pre_a),当pre_a == a 是 lcp 里需要特判,然后 rank[ a ] > rank[ pre_a ] 交换一下,使 a 的rank 在前面,rmq 查询的区间应该是 rmq(rank[ a ] + 1,rank[ pre_a ]),注意,这里要+1,因为 height[ i ] 表示的是后缀 sa[ i ] 和sa[ i - 1] 的公共前缀。

暴力代码如下:

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

typedef __int64 lld;

const int MAXN = 100011;

char str[MAXN];

int main()
{
    while(~scanf("%s",str))
    {
        int n;
        scanf("%d",&n);
        int pre_a = -1 ,pre_b = -1;
        int a,b;
        lld ans1 = 0,ans2 = 0;
        for(int i = 0;i<n;i++)
        {
            scanf("%d%d",&a,&b);
            ans1 += (b-a+1);
            if(a==pre_a)
            {
                if(b>pre_b)
                {
                    ans2 += b - pre_b;
                    int len = pre_b - a;
                    if(len == 0 ) ans2 ++ ;
                    while(len)
                    {
                        ans2++;
                        len /= 10;
                    }
                    ans2 += 2;
                }
                else
                {
                    int len = b - a;
                    if(len ==0) ans2 ++ ;
                    while(len)
                    {
                        ans2 ++ ;
                        len /= 10;
                    }
                    ans2 += 2;
                }
            }
            else
            {
                int dis = 0;
                for(int i = a,j = pre_a;i<b&&j<pre_b;i++,j++)
                    if(str[i]==str[j])
                        dis++;
                    else break;
                int len = dis;
                if(len == 0 ) ans2 ++ ;
                while(len)
                {
                    ans2++;
                    len /= 10;
                }
                ans2 += b - a - dis + 2;
            }
            pre_a = a;
            pre_b = b;
        }
        printf("%I64d %I64d\n",ans1,ans2);
    }
    return 0;
}


后缀数组代码如下:

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

typedef __int64 lld;

const int MAXN = 111111 ;

int t[MAXN],t2[MAXN],sa[MAXN],c[222];

char str[MAXN];

void build_sa(int n,int m)
{
    int *x = t,*y = t2;
    for(int i = 0;i<m;i++) c[i] = 0;
    for(int i = 0;i<n;i++) c[x[i] = str[i]] ++;
    for(int i = 1;i<m;i++) c[i] += c[i - 1];
    for(int i = n - 1;i>=0;i--) sa[--c[x[i]]] = i;

    for(int k = 1;k<n;k <<= 1)
    {
        int p = 0;

        for(int i = n - k ;i<n;i++) y[p++] = i;
        for(int i = 0;i<n;i++) if(sa[i] >= k) y[p++] = sa[i] - k;

        for(int i = 0;i<m;i++) c[i] = 0;
        for(int i = 0;i<n;i++) c[x[y[i]]] ++ ;
        for(int i = 1;i<m;i++) c[i] += c[i - 1];
        for(int i = n - 1;i>=0;i--) sa[--c[x[y[i]]]] = y[i];

        swap(x,y);
        x[sa[0]] = 0;
        p = 1;
        for(int i = 1;i<n;i++)
            x[sa[i]] = y[sa[i]] == y[sa[i - 1]] && y[sa[i] + k] == y[sa[i - 1] + k] ? p - 1 : p++;
        if(p>=n) break;
        m = p;
    }
}

int rank[MAXN],height[MAXN];

void get_height(int n)
{
    for(int i = 1;i<=n;i++)
        rank[sa[i]] = i;
    int k = 0;
    for(int i = 0;i<n;i++)
    {
        if(k) k--;
        int j = sa[rank[i] - 1];
        while(str[i + k] == str[j + k]) k ++;
        height[rank[i]] = k;
    }
   // for(int i = 1;i<=n;i++)
        //printf("i = %d,height[i] = %d,sa = %d\n",i,height[i],sa[i]);
}

int d[MAXN][111];

void init_rmq(int n)
{
    for(int i = 1;i<=n;i++)
        d[i][0] = height[i];
    for(int j = 1;(1<<j) <= n;j++)
        for(int i = 1;i + (1<<j) - 1 <=n;i++)
        {
            d[i][j] = min(d[i][j - 1] ,d[ i + (1<<(j - 1))][ j - 1 ]);
        }
}

int rmq(int a,int b)
{
    int k = 0;
    while((1<<(k+1)) < (b - a + 1)) k++;
    return min(d[a][k],d[b - (1<<k) + 1][k]);
}

int lcp(int a,int b,int n)
{
    if(a == b) return n - a ;
    a = rank[a],b = rank[b];
    if(a>b) swap(a,b);
    return rmq(a + 1 , b);
}

int main()
{
    while(~scanf("%s",str))
    {
        int len = strlen(str);
        build_sa(len+1,128);
        get_height(len);
        init_rmq(len);
        int q;
        scanf("%d",&q);
        int a,b;
        scanf("%d%d",&a,&b);
        lld ans1 = b - a + 1;
        lld ans2 = 1 + b - a + 2;
        q--;
        int pre_a = a,pre_b = b;
        while(q--)
        {
            scanf("%d%d",&a,&b);
            ans1 += b - a + 1;
            //printf("lcp = %d\n",lcp(pre_a,a,len));
            int tt = min(lcp(pre_a,a,len),min(b - a,pre_b - pre_a));
            int tmp = tt;
            if(tmp == 0) ans2 ++;
            while(tmp)
            {
                ans2 ++;
                tmp /= 10;
            }
            ans2 += b - a - tt + 2;
            pre_a = a;
            pre_b = b;
        }
        printf("%I64d %I64d\n",ans1,ans2);
    }
    return 0;
}




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值