[后缀数组+思路] hdu 4436 str2int

题意:给出n个字符串,求出所有字符串中出现的不同的整数和mod2012的值(即出现多次算一次)

思路:用后缀数组去重的方法很好想到,但是难点在于如何去快速的计算这些不同的子串。

比如两个串

123,124

他们的所有子串

原串:123#124

123#124

124

23#124

24

3#124

4

#124

而答案应是 1+12+123+124+2+23+24+3+4

那么我们该如何计算呢。

假设我们求的字符串 "12345"中的34,我们要怎么快速的求出。
我们定义了两个数组,一个是sum[]表示前缀和,一个是num[]表示数字,比如上面的例子
sum[0] = 1                                                          num[0] = 1 
sum[1] = 1 + 12                                                  num[1] = 12
sum[2] = 1 + 12 + 123                                        num[2] = 123
...                                                                            .....
sum[4] = 1 + 12 + 123 + 1234 + 12345              num[4] = 12345


我们首先将 sum[3] - sum[1] 得到 123 + 1234 , 而我们想要得到是 3 + 34
所以我们要减去 120 和 1200 即 num[1] * 110
用这样的方法就可以快速的求和


具体到我的样例

(1) 没重复     1+12+123=(sum[2]-sum[-1]-(num[-1]*ten[2--1]))-(sum[-1]-sum[-1]-(num[-1]*ten[-1--1]))

(2) 重复2个   124=1+12+124-1-12=(sum[6]-sum[3]-(num[3]*ten[6-3]))-(sum[5]-sum[3]-(num[3]*ten[5-3]))

(3)没重复       2+23=(sum[2]-sum[0]-(num[0]*ten[2-0]))-(sum[0]-sum[0]-(num[0]*ten[0-0]))

(4)重复1个     24=2+24-2=(sum[6]-sum[4]-(num[4]*ten[6-4]))-(sum[5]-sum[4]-(num[4]*ten[5-4]))

依次类推 记得去模就OK了

这里注意下数组下标会是-1 所以可以特判一下 不过其实也没啥 下标是-1的位置值为0

然后还有一个就是注意下  如果当前的前缀开头不是1~9 那么就跳过不计算

代码:

#include"cstdlib"
#include"cstdio"
#include"cstring"
#include"cmath"
#include"queue"
#include"algorithm"
#include"iostream"
using namespace std;
#define N 120123
int wa[N],wb[N],wv[N],wws[N];
int v[N],ra[N],sa[N],height[N],ten[N];
int num[N],sum[N],length[N];
char fuck[N];
int M=2012;
int cmp(int *r,int a,int b,int l)
{
    return r[a]==r[b]&&r[a+l]==r[b+l];
}
void da(int n,int m)
{
    int i,j,p,*x=wa,*y=wb;
    for(i=0; i<m; i++) wws[i]=0;
    for(i=0; i<n; i++) wws[x[i]=v[i]]++;
    for(i=1; i<m; i++) wws[i]+=wws[i-1];
    for(i=n-1; i>=0; i--) sa[--wws[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<n; i++) wv[i]=x[y[i]];
        for(i=0; i<m; i++) wws[i]=0;
        for(i=0; i<n; i++) wws[wv[i]]++;
        for(i=1; i<m; i++) wws[i]+=wws[i-1];
        for(i=n-1; i>=0; i--) sa[--wws[wv[i]]]=y[i];
        for(swap(x,y),i=1,p=1,x[sa[0]]=0; i<n; i++) x[sa[i]]=cmp(y,sa[i],sa[i-1],j)?p-1:p++;
    }
    return ;
}
void gethei(int n)
{
    int i,j,k=0;
    for(i=1; i<=n; i++) ra[sa[i]]=i;
    for(i=0; i<n; i++)
    {
        if(k) k--;
        j=sa[ra[i]-1];
        while(v[i+k]==v[j+k]) k++;
        height[ra[i]]=k;
    }
    return ;
}
int main()
{
    int n;
    ten[0]=0;
    for(int i=1;i<=N;i++) ten[i]=((ten[i-1]+1)*10)%M;  //构造0,10,110....数组
    while(scanf("%d",&n)!=-1)
    {
        int len=0;
        int i,j;
        for(i=0; i<n; i++)
        {
            scanf("%s",fuck);
            int l=strlen(fuck);
            int x=0,t=0;
            for(j=0; j<l; j++,len++)
            {
                v[len]=fuck[j];  
                length[len]=l-j;  //标记每段目标位置在哪
                t=t*10+fuck[j]-'0';  //累加求和
                t%=M;
                x+=t;
                x%=M;
                sum[len]=x%M;
                num[len]=t%M;
            }
            sum[len]=num[len]=0;  //分隔符的地方置零
            v[len++]='9'+i+1;    //添加分隔符
        }
        sum[len]=num[len]=0;
        v[len]=0;
        da(len+1,12000);
        gethei(len);
        int ans=0;
        for(i=1; i<=len; i++)  //sa是从1开始的 1~len
        {
            if(v[sa[i]]>='1'&&v[sa[i]]<='9')  //是不是1~9
            {
                int s=sa[i]-1,t1=s+length[sa[i]],t2=s+height[i];  //s为原始位置  t1为目标位置 t2为重复位置如题解所说
                int sum1,sum2;
                if(s<0)  //特判
                {
                    sum1=sum[t1];
                    sum2=sum[t2];
                }
                sum1=sum[t1]-sum[s]-num[s]*ten[t1-s];
                sum2=sum[t2]-sum[s]-num[s]*ten[t2-s];
                ans+=sum1-sum2;
                ans=(ans%M+M)%M;   //防止负数
            }
        }
        printf("%d\n",ans%M);
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值