【Good Bye 2020 G】Song of the Sirens

题目链接

链接

翻译

给你一个字符串 \(s_i\) 的生成规则,\(s_{i+1}=s_it_is_i\)

因为 \(t\) 的长度为 \(n\), 所以一直会生成到第 \(n+1\) 个字符串。

然后给你 \(Q\) 个询问,第 \(i\) 个询问会问你字符串 \(w\)\(s_k\) 中出现的次数。

题解

KMP,思维,前缀和。

大概的做法就是,\(w\) 的出现次数是前一首歌的两倍,然后加上中间新增的那个字符可能的贡献次数,然后递推一下

找到第一个大于等于 \(w\) 的歌曲 \(idx\), 对于 \(w\)\(s[idx]\) 中的次数,直接 \(KMP\), 后续的 \(g(i,w)\) (\(g(i,w)\)\(w\)\(s_i\) 中包含了 \(t_i\) 这个字符的匹配数)可以发现就是 \(w\)

\(s[idx]\) 的前端和末尾的匹配,也用 \(KMP\) 做就行。

照着官方题解写的。

重定向一下连接 我是链接

在代码中一些比较难懂的地方加了注释, 具体看代码中的注释吧。

代码

#include <bits/stdc++.h>
#define LL long long
using namespace std;

const int MOD = 1e9 + 7;
const int N = 1e5;
const int MAXW = 1e6;

int n, q;
int _pow[N + 10];
//sum[i][j] 表示的是t的前 i 个字符中 j出现的次数,以及每个出现位置乘上对应的2的幂次权重之后的结果。
LL sum[N + 10][26 + 5];
vector<string> vS;
string s0, t;

/*
	先算2^p
*/
void pre() {
	_pow[0] = 1;
	for (int i = 1; i <= N; i++) {
		_pow[i] = _pow[i - 1] * 2 % MOD;
	}
	//得到长度不超过10^6的所有的s
	vS.push_back(s0);
	int len = s0.size();
	int idx = 0;
	while (idx < n && len < MAXW) {
		string si = vS.back();
		string sipo = si + t[idx] + si;
		vS.push_back(sipo);
		idx++;
		len = len * 2 + 1;
	}
	for (char key = 'a'; key <= 'z'; key++) {
		int j = key - 'a';
		for (int i = 0; i < n; i++) {
			sum[i + 1][j] = 2*sum[i][j] + (key == t[i]);
			sum[i + 1][j] %= MOD;
		}
	}
}

//f 会返回最长的前后缀长度。
vector<int> doKMP(string s) {
	vector<int> f((int)s.size(), 0);
	int len = s.size();
	int j = 0;
	for (int i = 1; i < len; i++) {
		while (j > 0 && s[i] != s[j]) {
			j = f[j - 1];
		}
		if (s[i] == s[j]) {
			j++;
		}
		f[i] = j;
	}
	return f;
}

//返回 w 和 s[idx] 前/尾  尾/前的匹配情况。
vector<bool> getMatch(vector<int> f,int lenw) {
	vector<bool> can(lenw + 1, false);
	int cur = f.back();
	while (cur > 0) {
		can[cur] = true;
		cur = f[cur - 1];
	}
	//匹配 0 个的话,直接算成功。
	can[cur] = true;
	return can;
}

int answer(int k, string w) {
	//先找到第一个长度大于等于 w 的s[k]
	int idx = 0;
	int lenw = w.size();
	while ((int)vS[idx].size() < lenw) {
		idx++;
	}
	//vS[idx].size() >= lenw
	//如果s[k]的长度小于 w,那么直接拉闸。
	if (idx > k) {
		return 0;
	}
	//s[idx]是第一个大于等于w的
	//首先计算w在 s[idx]中出现的次数,这个用KMP搞。
	//为了同时便于做w开头一部分和s[idx]尾部的匹配,以及w尾部的一部分和s[idx]开头的匹配
	//因为后面的s[idx..k]都是 类似 a,s[idx],t[x],s[idx],b 这样的形式,所以 包含中间 t[x]的话
	//一定是 s[idx]的尾部以及 s[idx]的头部分别和 w的头部、尾部匹配。
	//因此,我们把 w 放在 s[idx] 的开头,做一下KMP,以及把 W 放在 s[idx] 的末尾再做一下KMP。
	string tmp = w + "#" + vS[idx];
	vector<int> fPre = doKMP(tmp);
	tmp = vS[idx] + "#" + w;
	vector<int> fSuf = doKMP(tmp);

	//获取w的前部在s[idx]的尾部的匹配情况
	vector<bool> mPre = getMatch(fPre,lenw);
	//获取w的尾部在s[idx]的前部匹配情况。
	vector<bool> mSuf = getMatch(fSuf,lenw);

	//先计算前面的 f(idx,w)*2^{n-idx}
	LL temp = 0;
	//计算w在s[idx]中的匹配数
	for (int x : fPre) {
		if (x == lenw) {
			temp++;
		}
	}
	temp = temp * _pow[k - idx] % MOD;

	//计算∑_{i=idx+1}^{k}f(i,w)*2^{n-i}
	for (int i = 1; i <= lenw; i++) {
		if (!mPre[i - 1] || !mSuf[lenw - i]) {
			continue;
		}
		int j = w[i - 1] - 'a';
		//s[idx+1..k] 这些歌曲,中间有多少个 j, 就能匹配多少次,所以要加上这一段的 sum
		temp = temp + sum[k][j] - sum[idx][j] * _pow[k - idx]%MOD;
		temp %= MOD;
		temp = (temp + MOD) % MOD;
	}
	return temp;
}

int main() {
	#ifdef LOCAL_DEFINE
		freopen("in.txt", "r", stdin);
	#endif // LOCAL_DEFINE

	//输入n,q,s0,t
	cin >> n >> q;
	cin >> s0 >> t;
	pre();
	while (q--) {
		int k;
		string w;
		cin >> k >> w;
		cout << answer(k, w) << endl;
	}
	return 0;
}
posted @ 2021-02-21 15:23  AWCXV  阅读(164)  评论(0编辑  收藏  举报