2024“钉耙编程”(4)——1009(昵称检索)

题目:

给定一个字符串 S,请计算从中删去一部分字符后(可以什么都不删),可以得到多少个本质不同的''昵称''。
一个字符串被认为是''昵称''当且仅当它可以被划分成前后两个部分,第一个部分是给定的 n 个名字之一,第二个部分长度固定四位,表示生日。
例如第一个样例中,可以得到的''昵称''分别为:''kevin0724''、''kevin0729''、''kevin0924''、''kevin0929''。

输入:

第一行包含一个正整数 T (1≤T≤100),表示测试数据的组数。

每组数据第一行包含两个正整数 n,m (1≤n≤105, 1≤m≤106),分别表示名字的数量以及字符串 S 的长度。

第二行包含一个长度为 m 的字符串 S,由数字和小写英文字母构成。

接下来 n 行,每行一个长度在 [1,20] 之间的仅由小写英文字母构成的字符串 namei,表示一个名字。输入数据保证名字两两不同。

输入数据保证 ∑m≤7000000,且 ∑|name|≤7000000。 

输出:

对于每组数据输出一行一个整数,即能得到的本质不同的''昵称''数量。 

 题解:

对于一个 昵称 来说,它由名字和生日两个部分拼接而成,如果它是 S 的子序列,那么
名字匹配的位置应该尽可能靠左,生日匹配的位置应该尽可能靠右。
1:预处理出 mp [ i ][ j ] 表示 S [ i, n ] 中字符 j 最靠左的出现位置的下标。
2:利用 mp 数组,对于每个名字可以求出从左往右贪心匹配时,它的最后一个字符在哪个下标最小的位置匹配上,记为 a i。
3: 同理可以倒着贪心求出每个生日的第一个字符在哪个下标最大的位置可以匹配上,记为 b i
问题现在转化为统计有多少对 ( i, j ) 满足 a i < b j 。枚举 i ,利用前缀和统计 j 的个数即可。
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define endl '\n'
const int N = 1e6 + 10, M = 1e3 + 10;
int n, m, ans[N];
int day[15] = {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
string s, a;
int mp[N][26], vis[26];

int fun(int * u){  // 把p数组引用过来 
	int x = m + 1;
	for(int i = 3; ~i; i --){
		x --;
		if(x < 1) return 0; // 无法整合出当前时间的值 
		x = mp[x][u[i]]; // 从后往前找 层层找 
	}
	return x; //符合当前月份且存在的条件 
}

int pp(){
	int x = 0;
	for(int i = 0; i < a.size(); i ++){ 
		x ++;
		if(x > m) return m + 1; //该字符位于后面最后面 
		x = mp[x][a[i] - 'a']; //类似于字典树 
	}
	return x;
}

signed main(){
	IOS
	int T;
	cin >> T;
	while(T --){
		cin >> n >> m;
		cin >> s;
		for(int i = 0; i < 10; i ++) vis[i] = 0;
		for(int i = 1; i <= m; i ++){
			if(s[i - 1] >= '0' && s[i - 1] <= '9') vis[s[i - 1] - '0'] = i;
			for(int j = 0; j <= 9; j ++) mp[i][j] = vis[j];   //预处理每个数字的位置,mp[i][j]——>从左到右在s字符串位于小标i下最靠近j的字符的下标。 
		}
		for(int i = 0; i <= m + 1; i ++) ans[i] = 0;  //清空 
		for(int i = 1; i <= 12; i ++){
			for(int j = 1; j <= day[i]; j ++){
				int p[4]; //在月日范围 把每一位记录在p数组中 
				p[0] = i / 10, p[1] = i % 10, p[2] = j / 10, p[3] = j % 10;
				int x = fun(p);
				if(x) ans[x] ++; // 符合条件的 当前月份值存在加1。 
			}
		}
		for(int i = m; i > 1; i --) ans[i - 1] += ans[i]; //前缀和, 直接计算 当前位置下的个数。 
		for(int j = 0; j < 26; j ++) vis[j] = m + 1;
		for(int i = m; i; i --){
			if(s[i - 1] >= 'a' && s[i - 1] <= 'z') vis[s[i - 1] - 'a'] = i;
			for(int j = 0; j < 26; j ++) mp[i][j] = vis[j];  //同理预处理每个字符的位置。 
		}
		int sum = 0;
		while(n --){
			cin >> a;
			int x = pp();
			if(x < m) sum += ans[x + 1];//ans从1开始所以x要 + 1。 
		}
		cout << sum << endl;
	}
	return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值