CF1187B-Letters Shop(字母商店)(字符串相关)

该博客讨论了一个关于字母商店的问题,其中字符串s作为字母展示,朋友们试图估算购买多少字母来构造他们的名字。每个朋友的名字都可以在s中找到构造方案。博客提到了使用数据结构优化解决这个问题的方法,包括数组和链式前向星等策略。
摘要由CSDN通过智能技术生成

The letters shop showcase is a string ss, consisting of n lowercase Latin letters. As the name tells, letters are sold in the shop.

字母商店的陈列是一个字符串s,包含n个小写拉丁字母,正如它的名字所说,这个店铺卖字母。

Letters are sold one by one from the leftmost to the rightmost. Any customer can only buy some prefix of letters from the string ss.

字母从最左到最右一个一个的出售,所有顾客都只能从字符串s中买一些前缀。

There are mm friends, the ii-th of them is named titi. Each of them is planning to estimate the following value: how many letters (the length of the shortest prefix) would s/he need to buy if s/he wanted to construct her/his name of bought letters. The name can be constructed if each letter is presented in the equal or greater amount.

有m个朋友,第i个人叫ti,他们中的每个人都打算去估计如下方案的价格:要买多少字母(所需最短前缀长度)他才能用这些买了的字母构造出他的名字。当且仅当库存字母大于或等于所需字母时,名字才可以被表示。

  • For example, for ss="arrayhead" and titi="arya" 5 letters have to be bought ("arrayhead").
  • For example, for ss="arrayhead" and titi="harry" 6 letters have to be bought ("arrayhead").
  • For example, for ss="arrayhead" and titi="ray" 5 letters have to be bought ("arrayhead").
  • For example, for ss="arrayhead" and titi="r" 2 letters have to be bought ("arrayhead").
  • For example, for ss="arrayhead" and titi="areahydra" all 9 letters have to be bought ("arrayhead").

It is guaranteed that every friend can construct her/his name using the letters from the string ss.

题目保证这些人的名字都可以在s中找到构造方案。

Note that the values for friends are independent, friends are only estimating them but not actually buying the letters.

注意每个人的方案估值都是独立的,也就是说他们只评估但实际上不买字母(每个样本彼此独立)。

Input

The first line contains one integer nn (1≤n≤2⋅1051≤n≤2⋅105) — the length of showcase string ss.

The second line contains string ss, consisting of exactly nn lowercase Latin letters.

The third line contains one integer mm (1≤m≤5⋅1041≤m≤5⋅104) — the number of friends.

The ii-th of the next mm lines contains titi (1≤|ti|≤2⋅1051≤|ti|≤2⋅105) — the name of the ii-th friend.

It is guaranteed that ∑i=1m|ti|≤2⋅105∑i=1m|ti|≤2⋅105.

Output

For each friend print the length of the shortest prefix of letters from ss s/he would need to buy to be able to construct her/his name of them. The name can be constructed if each letter is presented in the equal or greater amount.

对于每个朋友,输出构造出名字所需购买的前缀的最小长度。如果库存大于或等于所需,这个名字才能被表示。

It is guaranteed that every friend can construct her/his name using the letters from the string ss.

题目保证这些人的名字都可以在s中找到构造方案。

Example

input

Copy

9
arrayhead
5
arya
harry
ray
r
areahydra

output

Copy

5
6
5
2
9

这个题看起来像是需要构造一个类似于桶这样的东西,也就是说是把数据的个数存起来,利用这些“数目”去解题。一个桶可以用来存各个字母在字符串s中出现的次数,但是这个题只有2s的限时的同时却有巨大的数据量,所以必须再做优化。

我们可以考虑构造一个数组,用来指向每个字母出现的次数对应的位置,比如arrayhead这个字符串中,a出现第二次的位置就是4,所以据此就可以知道构造"aa"至少需要4个长度的前缀串。但是这并不代表你可以跳着访问这个字符串,因为第二个桶需要保存质询字符串的字幕出现次数,如果你跳过的话这中间的统计无法进行,会导致答案错误。这样优化的好处是可以免去在遍历质询字符串时需要对两个桶之间的比对(也就是说看看库存是否已经满足所需了)。

显然,如果第二个字符串中出现了某个原字符串中没出现过或者数量少于所需的字母,那么这个就无法正常统计。由于题目中会强行使得答案成立,所以只需输出原字符串的长度即可,这也是对时间的优化之一,如果你更新后的结果已经是字符串最大长度了,那么也可以直接跳出分析输出答案。

所以有如下的思路:

  1. 建立统计原字符串中各字母出现次数,原字符串中第n次出现某个字符对应的位置,输入的质询字符串中的各字母出现次数的若干个数组
  2. 遍历统计原字符串相关数据
  3. 对于每一次输入,初始化相关数组后对其进行统计所需数据,并及时进行剪枝。
#include<pch.h>
#include <iostream>
#include <cstdio>
#include <bits/stdc++.h>
#include <map>
#include <algorithm>
#include <stack>
#include <iomanip>
#include <cstring>
#include <cmath>
#define DETERMINATION main
#define lldin(a) scanf("%lld", &a)
#define println(a) printf("%lld\n", a)
#define reset(a, b) memset(a, b, sizeof(a))
const int INF = 0x3f3f3f3f;
using namespace std;
const double PI = acos(-1);
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
const int mod = 1000000007;
const int tool_const = 19991126;
const int tool_const2 = 33;
inline ll lldcin()
{
	ll tmp = 0, si = 1;
	char c;
	c = getchar();
	while (c > '9' || c < '0')
	{
		if (c == '-')
			si = -1;
		c = getchar();
	}
	while (c >= '0' && c <= '9')
	{
		tmp = tmp * 10 + c - '0';
		c = getchar();
	}
	return si * tmp;
}
///Untersee Boot IXD2(1942)
/**Although there will be many obstructs ahead,
the desire for victory still fills you with determination..**/
/**Last Remote**/
char origin[200010], current[800010];
ll o_freq[100], c_freq[100], max_reference[100][200010];
//三个数组:原字符串字母出现频率,现字符串字幕出现频率,次数指向位置
int DETERMINATION()
{
	ll len;
	cin >> len;
	cin >> origin;
	for (int i = 0; origin[i]; i++)
	{
		o_freq[origin[i] - 'a']++;//这个字母出现次数加一
		max_reference[origin[i] - 'a'][o_freq[origin[i] - 'a']] = i;
       //这个字母出现第n次时对应的位置是i
	}
	ll t;
	cin >> t;
	while (t--)
	{
		reset(current, '\0');
		reset(c_freq, 0);
		cin >> current;
		ll max_reach = -1;
		bool sign = false;
		if (strlen(current) > len)
		{
			cout << len << endl;
		}//由于题目中没说两个字符串大小关系,所以还是小心为妙
		else
		{
			for (int i = 0; current[i]; i++)
			{
				c_freq[current[i] - 'a']++;//当前字符串字幕出现频率
				if (c_freq[current[i] - 'a'] > o_freq[current[i] - 'a'])//如果库存不能满足需求,退出
				{
					sign = true;
					break;
				}
				max_reach = max(max_reach, max_reference[current[i] - 'a'][c_freq[current[i] - 'a']]);
				if (max_reach == len-1)//如果最大指向已经是字符串最大长度,直接退出
					break;
			}
			if (sign == false)
				cout << max_reach + 1 << endl;
			else
			{
				cout << len << endl;
				continue;
			}
		}
	}
	return 0;
}

 另外一个办法是类似于链式前向星之类的数据结构存储原字符串中某个字母的首次出现位置和下一次出现的位置。

这需要倒序遍历原字符串。一个数组用来标记它的某个字母出现的位置,另一个数组用来标记上一个位置需要直接指向的下一个位置,也就是说对于一个字母,它下一次出现在哪里。为什么倒序遍历呢?这相当于标记首位置的数组先走一步,然后记录指向的数组跟进标记,如此做成一个网络。

那么在分析质询字符串时,如果这个字母是首次出现,那么至少在目前,这个字母需要“之前标记好的”长度的前缀。如果不是首次出现,那么这个“目前长度”就得变成下一个字母出现的位置的数值。

所以有如下思路:

  1. 对原字符串进行分析,形成一个传递网络
  2. 对质询字符串进行分析,得到答案
#include<pch.h>
#include <iostream>
#include <cstdio>
#include <bits/stdc++.h>
#include <map>
#include <algorithm>
#include <stack>
#include <iomanip>
#include <cstring>
#include <cmath>
#define DETERMINATION main
#define lldin(a) scanf("%lld", &a)
#define println(a) printf("%lld\n", a)
#define reset(a, b) memset(a, b, sizeof(a))
const int INF = 0x3f3f3f3f;
using namespace std;
const double PI = acos(-1);
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
const int mod = 1000000007;
const int tool_const = 19991126;
const int tool_const2 = 33;
inline ll lldcin()
{
	ll tmp = 0, si = 1;
	char c;
	c = getchar();
	while (c > '9' || c < '0')
	{
		if (c == '-')
			si = -1;
		c = getchar();
	}
	while (c >= '0' && c <= '9')
	{
		tmp = tmp * 10 + c - '0';
		c = getchar();
	}
	return si * tmp;
}
///Untersee Boot IXD2(1942)
/**Although there will be many obstructs ahead,
the desire for victory still fills you with determination..**/
/**Last Remote**/
char tmpa[300000], tmpb[300000];
ll heada[150], nexta[300000], guidance[150];
int DETERMINATION()
{
	ll n;
	cin >> n;
	cin >> tmpa;
	for (int i = n-1; i >= 0; i--)
	{
		nexta[i] = heada[tmpa[i] - 'a' + 1];//下次出现的位置
		heada[tmpa[i] - 'a' + 1] = i;//首次出现的位置
	}
	ll t;
	cin >> t;
	while (t--)
	{
		reset(guidance, -1);//初始化,由于之前的位置有0,所以这里初始化为-1
		reset(tmpb, '\0');
		cin >> tmpb;
		ll tans = 0, ans = 0;
		for (int i = 0; tmpb[i]; i++)
		{
			if (guidance[tmpb[i] - 'a' + 1] == -1)//判断某个字母是否首次出现
			{
				guidance[tmpb[i] - 'a' + 1] = heada[tmpb[i] - 'a' + 1];//把指针移到首次出现的位置上
				tans = guidance[tmpb[i] - 'a' + 1];
			}
			else
			{
				guidance[tmpb[i] - 'a' + 1] = nexta[guidance[tmpb[i] - 'a' + 1]];
//把指针移到下次出现的位置上
				tans = guidance[tmpb[i] - 'a' + 1];
			} 
			ans = max(ans, tans);
		}
		cout << ans+1 << endl;
	}
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值