JZOJ 3159. 【JSOI2013】广告计划(字符串哈希)

JZOJ 3159. 【JSOI2013】广告计划

题目

Description

如今,在建筑的墙面上或者篱笆桩的表面上涂上一些广告,是一种新的吸引眼球的方法。

现在,小G运营的一家小公司,决定也试着这样做做广告。小G在他的篱笆桩上腾出了一些地方供广告使用。每一个篱笆桩都是一个水平的 1 ∗ 1 ∗ L 1*1*L 11L 的四棱柱,其中有一个 1 ∗ L 1*L 1L的面是可以做广告的。 1 ∗ L 1*L 1L的面上划出了 L L L 1 ∗ 1 1*1 11的小正方形(更具体地说是连续 L L L个水平排列的正方形),每个正方形内写上一个字母。

时间久了,广告做多了难免会出现一些比较麻烦的情况,比如计划改变或者制作出错,因此小G的仓库里面积累了好多没有用的,上面已经写上 L L L个字母的篱笆桩。(所有的篱笆桩的大小都是一样的,他们唯一的区别仅仅在于上面写了什么字母)。

小G决定对于这些篱笆桩进行重新利用,并且有了一些新的想法。

如果将这些篱笆桩竖直的叠放起来,并且依次从左往右,对于每一个篱笆桩顺次从上到下读出上面的字母,那么我们可以得到一些新的比较长的单词,如下图:
在这里插入图片描述
这些新的单词能满足小G 的一些新的需要。当然,基于美学考虑,小G 是不允许你删改篱笆桩上已经写上的字母的。

我们更具体地描述这个过程。我们将 K K K个长度为 L L L的篱笆桩叠在一起,可以得到一个写有 K K K L L L列共 K ∗ L K*L KL个字母的面,每一个字母都在对应的唯一的格子里。我们从左上角开始依次向下读出每一个字母可以得到一个字母的序列,比如上图中的这个例子,那么我们读出的结果就是“TOEIIZENITKN”。如果,这个串中有我们所需要的单词,那么显然我们只需要将一些格子刷白,就可以得到我们所需要的了。举个例子,比如小G想要给圣彼得堡的足球队泽尼特队做个广告,那么很显然只要按照上图中的做法就可以达到小G想要的效果了。

现在小G已经想好了要做怎样的广告,同时也提供给你了小G仓库中的篱笆桩的类型的描述,你可以认为每一种类型的篱笆桩都是有无数个的。现在小G想知道至少需要多少个篱笆桩叠起来才可以做出小G想要的广告。

Input

第一行两个自然数, N N N L L L

接下来 N N N行,每行包含 L L L个小写字母,描述一种篱笆桩上写着的字母。保证不同类型的篱笆桩上的字母序列都是不一样的,每一种类型的篱笆桩都是有无数个。

最后一行是一个长度不超过 200 200 200的字符串 s s s,表示小G想好的广告词。

Output

第一行一个整数 K K K,表示所需要的最少数量的篱笆桩(注意是数量,并不是种类数)。

接下来一行包含 K K K个数,表示具体叠放的方案,你需要从上到下依次输出当前位置的篱笆桩是第几类的。类型的编号等同于其在输入文件中的位次。相邻两数之间严格用一个空格隔开,行首和行末不能有多余的空格。

如果有多解,输出任意一个就可以,如果无解,输出一行一个数 − 1 -1 1

Sample Input

【输入样例1】
3 4
tiet
oink
ezin
zenit

【输入样例2】
2 11
sillysample
happysample
sam

【输入样例3】
2 3
baa
aab
bb

【输入样例4】
2 3
aaa
bbb
cc

Sample Output

【输出样例1】
3
1 2 3

【输出样例2】
1
2

【输出样例3】
2
1 1

【输出样例4】
-1

Data Constraint

对于30%的数据满足: N ≤ 10 , L ≤ 100 N≤10,L≤100 N10L100.
对于100%的数据满足: N ≤ 100 , L ≤ 100 N≤100,L≤100 N100L100.

题解

  • 题目大意如下:
  • 给出 N N N个长度为 L L L的仅由小写字母组成的字符串,
  • 要求从中选出 K K K个字符串,按一定顺序排列,形成一个矩阵。
  • 使它可以从某个位置开始,纵向向下选出连续的若干个字符(如果到最后一行则变为下一列的第一虛,如上图),可以组成给定的字符串 s s s
  • 要求最小化 K K K,并输出任意一种选字符串的方案。
  • 这题有暴力的做法,可以过,但有点慢,
  • 这里介绍一种更快的方法!
  • 先把 N N N个字符串中的所有子串(也就是同一行中任意开头,任意结尾的每一个串),以 27 27 27进制压成一个整数,放入一个哈希表中,记录下所在的行开头的列
  • 接下来从小到大枚举答案 K K K,如果某一个 K K K有可行的方案,即可直接输出了,
  • 再找出当前的 K K K下,字符串 s s s呈现的形态( K K K个字符串),
  • 如: s = s= s=abcde, K = 3 K=3 K=3,会变成如下三个串(下文提到的“串”都是 s s s变出来的串),
  • ad
  • be
  • c
  • 尽管a不一定出现在第一行,也是这三个串,
  • a在第二行(“-”代表空格):
  • -c
  • ad
  • be
  • 要注意这样每个串的开头位置会有不同
  • a在第三行:
  • -be
  • -c
  • ad
  • 很显然,就不再举例了……
  • 把这些串以前面同样的形式压缩,在哈希表里面查找,
  • 找到第 i i i个串,它的开头列为 j j j,所在第 h h h行(读入的行),则 b z [ i ] [ j ] = h bz[i][j]=h bz[i][j]=h,表示第 i i i个串开头位置为 j j j可以在读入的第 h h h行找到。
  • 如果有多行都可以找到也无所谓,都是一样的,不影响答案。
  • 再枚举 s [ 1 ] s[1] s[1]出现在哪一行( i ∈ [ 1 , K ] iin[1,K] i[1,K]),枚举 s [ 1 ] s[1] s[1]出现在哪一列( j ∈ [ 1 , L ] jin[1,L] j[1,L]
  • b z bz bz中查找在此条件下, s [ 1 ] s[1] s[1]所在的整列(某些开头位置可能 + 1 +1 +1)都有 h h h值,即为成立。
  • 也就是某些 b z [ i ] [ j ] bz[i][j] bz[i][j]和另一些 b z [ i ] [ j + 1 ] bz[i][j+1] bz[i][j+1]是否有值。
  • 这里的某些是根据 i i i的值来决定的,自己推一下就好。

代码

#include<cstdio>
#include<cstring>
using namespace std;
#define md 2999999
char s[101][101],ss[201],st[201];
int len=0,last[5000001],next[505001],tov[505001][2];
int bz[201][101],by[201][101],id=0;
int e[110],hx[5000001];
void add(int x,int i,int j)
{
	tov[++len][0]=i,tov[len][1]=j;
	next[len]=last[x];
	last[x]=len;
}
void hash(int t,int i,int j)
{
	int x=t;
	while(hx[x])
	{
		if(hx[x]==t) 
		{
			add(x,i,j);
			return;
		}
		x=(x+1)%5000001;
	}
	hx[x]=t;
	add(x,i,j);
}
void find(int t,int j)
{
	int x=t;
	while(hx[x])
	{
		if(hx[x]==t)
		{
			for(int i=last[x];i;i=next[i]) bz[j][tov[i][1]]=tov[i][0],by[j][tov[i][1]]=id;
			return;
		}
		x=(x+1)%5000001;
	}
}
int main()
{
	int n,m,l,i,j,k,h;
	scanf("%d%d
",&n,&l);
	e[0]=1;
	for(i=1;i<=100;i++) e[i]=e[i-1]*27%md;
	for(i=1;i<=n;i++)
	{
		scanf("%s
",s[i]+1);
		for(j=1;j<=l;j++)
		{
			int t=1;
			for(k=j;k<=l;k++)
			{
				t=(t+(s[i][k]-'a'+1)*e[k-j+1]%md)%md;
				st[k-j+1]=s[i][k];
				hash(t,i,j);
			}
		}
	}
	scanf("%s",ss+1);
	m=strlen(ss+1);
	for(k=1;k<=m;k++)
	{
		id++;
		for(j=1;j<=k;j++)
		{
			int t=1,ln=0;
			h=j;
			while(h<=m)	
			{
				st[++ln]=ss[h];
				t=(t+(ss[h]-'a'+1)*e[ln]%md)%md;
				h+=k;
			}
			find(t,j);
		}
		for(i=1;i<=k;i++)
		{
			int i1=k-i+1;
			for(j=1;j<=l;j++)
			{
				int ok=1;
				for(h=1;h<=k;h++) 
				{
					if((h>i1&&by[h][j+1]<id)||(h<=i1&&by[h][j]<id)) 
					{
						ok=0;
						break;
					}
				}
				if(ok)
				{
					printf("%d
",k);
					for(h=i1+1;h<=k;h++) printf("%d ",bz[h][j+1]);
					for(h=1;h<=i1;h++) printf("%d ",bz[h][j]);
					return 0;
				}
			}
		}
	}
	printf("-1");
	return 0;
}
哈哈哈哈哈哈哈哈哈哈
原文地址:https://www.cnblogs.com/LZA119/p/13910084.html