专栏文章

题解:P5270 无论怎样神树大人都会删库跑路

P5270题解参与者 2已保存评论 1

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
1 条
当前快照
1 份
快照标识符
@miqee6io
此快照首次捕获于
2025/12/04 03:26
3 个月前
此快照最后确认于
2025/12/04 03:26
3 个月前
查看原文
因为放不了图片,所以代码注释我会写的详细一些。
用桶排序也能做,但是代码会繁琐一些,而且时间卡的很极限,所以我还是选择了字符串哈希。
思路还是很简单的,就是模拟一段 XX 字符串的增加过程,找到规律后跳过后面的模拟过程(全过程模拟一遍正常会超时),然后直接得到答案。
这题的难点在于字符串的哈希处理和规律的查找,哈希处理因人而异,哈希函数五花八门我就不展开了,能用就行,剩下的都是代码功底。
规律的发现还是很有意思的,我们以 XX 字符串的 RR 次添加为一次循环会发现,除了第一次循环得到的答案不一样,其他的每次循环答案都一样,那我们只需要记录第一次循环的答案和第二次循环的答案,然后求出剩下的循环次数乘上第二次循环的答案加上第一次循环的答案不就是总答案了吗?
哈哈,是这样的,但是被hack了(悲)。为啥被hack了呢?原因出在“我们以 XX 字符串的 RR 次添加为一次循环”这个前提条件上,这一次循环就一定能出答案吗?未必。
这里感谢大佬慷慨的提供了hack数据(不然又要调试半天)。我们发现在 RR 值很小并且 SS 字符串长度很大的情况下,一次循环连长度都凑不齐还比较什么,就会导致误判答案为零,实际上当长度凑齐后是有答案的。
为了那宝贵的答案,我们只能忍痛先添加多次循环直到超过 SS 字符串长度,再按照上述流程走,就可以愉快的通过啦。
CPP
#include<iostream>
#include<string>
#include<vector>
using namespace std;
//m数组存储所有小字符串的哈希值
long long N, T, Q, S = 0, a[10], m[100005], R, ans = 0, t[100005], ans1, ans2;
//P数组存储所有小字符串的每段的哈希值,方便切割成需要的长度
vector<long long>P[100005];
//V数组充当X字符串
vector<long long>V;
void go()
{
	//分别是 当前长度截取的哈希值、X字符串每段的下标、当前字符串的总长度
	long long sum = 0, i = V.size() - 1, Len = 0;
	while (true) {
		if (i < 0)break;//燃尽了,返回
		if (Len + P[V[i]].size() >= T)break;//满足长度要求了,返回
		Len += (P[V[i]].size());
		i--;
	}
	if (i < 0)return;//X字符串燃尽了,但是还不够多~
	if (Len + P[V[i]].size() < T)return;//如果长度不达标就返回
	if ((T - Len - 1) >= 0)sum += (P[V[i]][(T - Len - 1)]);//如果长度不是刚刚好,就需要切割一点字符串补上去,P数组就开始发力了
	for (i++; i < V.size(); i++) {
		sum += m[V[i]];//将前面点过名的字符串都取出哈希值
	}
	if (sum == S)ans++;//如果哈希值一样就说明,答案加一
}
int main()
{
	cin >> N >> T >> Q;
	a[0] = 1;
	for (long long i = 1; i < 10; i++) {
		a[i] = a[i - 1] * 911;//哈希函数初始化 911是素数,素数就行
	}

	for (long long i = 1,j; i <= T; i++) {
		cin >> j;
		if (j == 0)S += 1;//先将S字符串的哈希值算出来
		else while (j) {
			S += (a[j % 10]);
			j /= 10;
		}
	}
	for (long long i = 1,j; i <= N; i++) {
		cin >> j;
		for (long long k = 1; k <= j; k++) {
			cin >> t[k];//将小字符串正序录入数组
		}
		for (long long k = j; k >= 1; k--) {
			if (t[k] == 0)m[i] += 1;//倒序计算哈希值,为了方便切割
			else {
				while (t[k]) {
					m[i] += (a[t[k] % 10]);
					t[k] /= 10;
				}
			}
			P[i].push_back(m[i]);//将需要的字符串段落的哈希值录入
		}
	}
	cin >> R;

	for (long long i = 1,j; i <= R; i++)
	{
		cin >> t[i];
	}

	if (Q <= R * 2) {//如果两次循环就结束了,那么证明数据不是很大,可以直接处理
		for (long long i = 1; i <= Q; i++) {
			V.push_back(t[i%(R+1)]); go();//按顺序将小字符串扔到X字符串后面
		}
		cout << ans;
	}
	else {//两次处理不了就需要找规律,不然全遍历一遍很容易超时
		long long k = 0;
		while (ans == 0) {//初步处理,直到第一次答案出现为止
			for (long long i = 1; i <= R; i++) {
				V.push_back(t[i % (R + 1)]); go();
			}
			k++;//记录循环次数
			//当循环的次数实在过多,我们就认为没有任何一次循环有答案产生,再不退出就和全遍历没两样了
			if (k == 10000)break;//这个可以设置,目前k==3就可以AC这道题,正常不会超过S字符串的长度,S的最大长度为10^5
		}
		ans1 = ans;//记录第一次循环的答案
		ans = 0;
		for (long long i = 1; i <= R; i++) {
			V.push_back(t[i % (R + 1)]); go();
		}
		ans2 = ans;//记录第二次循环的答案
		long long sum = Q / R;//计算剩下的循环次数
		sum-=k;
		ans1 += (sum * ans2);//剩下的每一次循环我们都认为可以产生一次ans2的答案
		ans = 0;
		sum = Q % R;//看看有没有漏掉的次数,构不成循环的遍历
		for (long long i = 1; i <= sum; i++) {//计算剩下的答案
			V.push_back(t[i % (R + 1)]); go();
		}
		cout << ans1 + ans;
	}
	return 0;
}

评论

1 条评论,欢迎与作者交流。

正在加载评论...