Codeforeces 727 E 字符串双Hash 2017/1/12

题意:n个长度为k的字符串,首尾相接,按顺时针顺序写在一张CD上,每个字符串只写一次总字符串长度小于1e6。保证每个字符串不同。这样CD上就有了一个环形字符串,长度是n*k。又给出g个长度为k的字符串列表(编号从1到g),按照顺时针打印CD上字符串的编号。保证每个字符串不同。保证这g个字符串长度的和小于2*1e6。 
下面代码和思路参考了Wannafly每日一题,顺便学会了Hash。记录了模板。 

解题思路:可以发现,如果枚举第一个串的开始位置,由于输入的g个串,长度都为k,那么每个串的位置就固定了。那么我只要知道,在主串上那一段位置的字符串,是否存在在g个串中,然后如果每个都存在,那么就是符合的。这一部分可以用字符串hash做到O(1)判断。复杂度的话,枚举第一个串的复杂度是k,一共需要匹配n个字符串,所以总复杂度是O(nk)。要注意不能用自然溢出写,因为可以造出数据卡掉自然溢出。


转:http://blog.csdn.net/just_sort/article/details/54425377


#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N = 2e6+5;
const int seed=131;
int mod[2]={1000000007,1000000009};
char S[N];
int F[2][N];
int pre[2][N];

int n,g,w;
int tim,vis[N],ans[N];
map<pii,int> cnt;

void pre_init()
{
	for(int t=0;t<=1;t++)
	{
		F[t][0]=1;
		for(int i=1;i<N;i++)
			F[t][i]=(ll)F[t][i-1]*seed%mod[t];
	}
}

pii Hash(string s){
	int ret[2]={0};
	int len=s.size();
	for(int t=0;t<=1;t++)
		for(int i=0;i<len;i++)
			ret[t]=((ll)ret[t]*seed+s[i])%mod[t];
	return pii(ret[0],ret[1]);
}


void pre_solve()
{
	int len=strlen(S+1);
	for(int t=0;t<=1;t++)
	{
		pre[t][0]=0;
		for(int i=1;i<=len;i++)
			pre[t][i]=((ll)pre[t][i-1]*seed+S[i])%mod[t];
		for(int j=len+1,i=1;i<=w-1;i++,j++)
			pre[t][j]=((ll)pre[t][j-1]*seed+S[i])%mod[t];
	}
}

pii Hash(int l,int r)
{
	int ret[2];
	for(int t=0;t<=1;t++)
		ret[t]=(pre[t][r]-(ll)pre[t][l-1]*F[t][r-l+1]%mod[t]+mod[t])%mod[t];
	return pii(ret[0],ret[1]);
}

bool check(int id){
	tim++;
	int l=id,r=id+w-1;
	for(int i=1;i<=n;i++,l+=w,r+=w)
	{
		pii t=Hash(l,r);
		if(!cnt.count(t)) return 0;
		int p=cnt[t];
		if(vis[p]==tim) return 0;
		vis[p]=tim;ans[i]=p;
	}
	printf("YES\n");
	for(int i=1;i<=n;i++)
		printf("%d ",ans[i] );
	return 1;
}

int main(){
	pre_init();
	scanf("%d%d%s",&n,&w,S+1);
	pre_solve();
	scanf("%d",&g);
	for(int i=1;i<=g;i++)
	{
		scanf("%s",S);
		pii t=Hash(S);
		cnt[t]=i;
	}
	bool mark=0;
	for(int i=1;i<=w;i++)
	{
		if(check(i))
		{
			mark=1;
			break;
		}
	}
	if(!mark) printf("NO\n");
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值