ZOJ Monthly, July 2015 K-hash 后缀数组去重做法

本文详细介绍了ZOJ Monthly 2015竞赛中的一道题目,重点探讨了使用K-hash和后缀数组进行字符串去重的方法。通过实例解析算法实现过程,帮助读者理解如何高效地处理重复字符串。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:意思有一个数字串,然后问子串构成的数字,有几个模k为0,1,2...k-1的。然后要求数字不能重复,也不能有前导零

做法:
先不考虑0的问题。
(1)首先o(32n)计算出所有的模k为0,1,2...k-1的子串有几个。此时不考虑重复。即每次计算出到第i位位置,每种串有多少个。然后把第i个位置插进去就能算了。
(2)然后利用后缀数组去重。如果height[i]=j那么表示第i个串与第i-1个串有j个子串相同,把这j个串的值从之前计算出来的答案中减去就好了。这个过程如果暴力的话是n^2,我开个sum数组是每次把前次的结果记录下来。这样如果height[i+1]比height[i]要大的话,比多出来的串的值加到sum数组里面去,然后ans减去sum即可。。。
考虑0
在(1)中不插入0做头位置,在(2)中不去首位置为0的重。即可。
PS:要预处理一个哈希数组,这样可以o(1)得到i到j这一段子串构成的数字的值。
PSS:关于这个做法的复杂度,我认为是o(32n)左右,但是我并不会证明,因为形式上看起来好像是o(n^2)。关键在于height数组的上下浮动是否是o(n)级别的。。。。这个代码在zoj上跑了120ms,比赛中提交的代码里是最快了。。。。


#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#define maxn 55555
using namespace std;

int wa[maxn],wb[maxn],wv[maxn],wsa[maxn];
int ranks[maxn],height[maxn];

void calheight(int *r,int *sa,int n)
{
     int i,j,k=0;
     for(i=1;i<=n;i++) ranks[sa[i]]=i;
     for(i=0;i<n ;height[ranks[i++]]=k)
     for(k?k--:0,j=sa[ranks[i]-1];r[i+k]==r[j+k];k++);
     return;
}

int cmp(int *r,int a,int b,int l)
{
    return r[a]==r[b]&&r[a+l]==r[b+l];
}

void build_sa(int *r,int *sa,int n,int m)
{
     int i,j,p,*x=wa,*y=wb,*t;
     for(i=0;i<m;i++) wsa[i]=0;
     for(i=0;i<n;i++) wsa[x[i]=r[i]]++;
     for(i=1;i<m;i++) wsa[i]+=wsa[i-1];
     for(i=n-1;i>=0;i--) sa[--wsa[x[i]]]=i;
     for(j=1,p=1;p<n;j*=2,m=p)
     {
       for(p=0,i=n-j;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++) wsa[i]=0;
       for(i=0;i<n;i++) wsa[wv[i]]++;
       for(i=1;i<m;i++) wsa[i]+=wsa[i-1];
       for(i=n-1;i>=0;i--) sa[--wsa[wv[i]]]=y[i];
       for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++)
       x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
      }
      return;
}
int r[maxn],sa[maxn];
char str[55555];
int hash[55555],shi[55555];
int pan(char ch)
{
	return ch-'0';
}
int ans[111],k;
void INIT()
{
		int sum[100],add[111];
		bool ling=false;
		memset(sum,0,sizeof(sum));
		memset(add,0,sizeof(add));
		memset(ans,0,sizeof(ans));
		int len=strlen(str);
		for(int i=0;i<len;i++)
		{
			int dig=pan(str[i]);
		//	dig%=k;
			for(int j=k-1;j>=0;j--)
			{
				add[(j*10+dig)%k]+=sum[j];
			}
			if(dig!=0)
			add[dig%k]++;
			else ling=true;
			for(int j=0;j<=k;j++)
			{
				ans[j]+=add[j];
				sum[j]=add[j];
				add[j]=0;
			}
		}
		if(ling)ans[0]++;
}

int get_hash(int i,int j)
{
	if(i==0)return hash[j-1];
	int hi=hash[i-1];
	int hj=hash[i+j-1];
	hi=hi*shi[j];
	hi%=k;
	int det=hj-hi;
	while(det<0)det+=k;
	return det;
}
int main(){
	shi[0]=1;
	
	while(scanf("%s%d",str,&k)!=EOF){
		int n=strlen(str);
		for(int i=1;i<=n;i++)shi[i]=shi[i-1]*10%k;
		for(int i=0;str[i];i++)
			r[i]=(int)str[i];
		r[n]=0;
		build_sa(r,sa,n+1,130);
		calheight(r,sa,n);//height(0-n)
		hash[0]=pan(str[0])%k;
		for(int i=1;i<n;i++)
		{
			hash[i]=hash[i-1]*10+pan(str[i]);
			hash[i]%=k;
		}
		INIT();
		int last=0;
		int sum[33];
		memset(sum,0,sizeof(sum));
		int lf=-1;
		for(int i=2;i<=n;i++)
		{
			int first=sa[i];
			if(height[i]>last)
			{
				for(int j=last+1;j<=height[i];j++)
				{
					int o=get_hash(first,j);
					sum[o]++;
				}
			}
			else
			{
				for(int j=last;j>height[i];j--)
				{
					int o=get_hash(lf,j);
					sum[o]--;
				}
			}
			last=height[i];
			if(str[first]!='0')
			for(int j=0;j<k;j++)
			{
				ans[j]-=sum[j];
			}
			lf=first;
		}
		int  key=0;

		for(int i=0;i<k;i++)
		{
			printf("%d",ans[i]);
			if(i==k-1)printf("\n");
			else printf(" ");
		}
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值