EOJ 3486 二分+哈希

传送门:点击打开链接

读完题目之后有以下理解。

1. 最后的答案应该是某一个后缀。比如原字符串是123654,那么答案就是654。

2. 后导0是没有意义的,应该将其去掉。比如原字符串是123654000,那么答案还是654。

读懂题目意思之后,就知道这道题目是一个求最大后缀问题。很容易这样想:找到最大的那个数字所对应的后缀不就行了吗?比如原字符串是123654,最大的数字是6,其对应的后缀是654。

其实很容易推翻这种想法,比如原字符串是93295,刚才那种方法就不适用了。因为这里有多个最大值。

那么如何求最大后缀呢?显然,最后的答案肯定是最大值对应的后缀,但是有多个最大值的时候,我们就要判断一下哪个后缀更大了。当然可以一位一位的比较,但是太慢了。有一个比较好的方法就是:二分+哈希。

字符串哈希的基本知识请看这里:点击打开链接,本文用的就是这个博客的hash代码。

要想比较两个后缀,我们首先要计算出这两个后缀的最长公共前缀,然后再比较最长公共前缀的下一位即可。说起来比较抽象,看个例子就明白了。

原字符串是9321935,易知答案要么是9321935,要么是935。我们先计算这两个后缀的最长公共前缀,也就是2。这说明这两个后缀的前两位都是相同的,所以直接比较第三位(也就是2和5)即可。最后得到结论935这个后缀更大,所以答案就是它。

我们可以用二分答案法来二分出最长公共前缀的值。

二分法的判断条件是:如果长度为m的后缀是两个后缀的公共前缀(判断两个字符串的哈希值是不是相同即可),那么最长公共前缀长度肯定大于m。反之小于m。

上面的描述也说明了为什么可以用二分法,因为存在着某种单调性。

代码如下:

// C++Code.cpp: 定义控制台应用程序的入口点。
//
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#pragma warning(disable:4996)

using namespace std;

typedef unsigned long long ull;
const int N = 20005;
const ull base = 163;
ull Hash[N], p[N];
char s[N];
int len;

// 预处理hash表
void hash_init () 
{
	p[0] = 1;
	Hash[0] = 0;
	for (int i = 1; i <= len; i++)
		p[i] = p[i - 1] * base;
	for (int i = 1; i <= len; i++)
		Hash[i] = Hash[i - 1] * base + (s[i - 1] - '0');
}

// 得到[l, r]区间内字符串的hash值
ull hash_code (int l, int r) 
{
	return Hash[r] - Hash[l - 1] * p[r - l + 1];
}

// 通过哈希值判断[s1, s1 + length - 1]和[s2, s2 + length - 1]是否相同
bool C (int s1, int s2, int length)
{
	int t1 = s1 + length - 1;
	int t2 = s2 + length - 1;
	if (t1 >= len || t2 >= len)
		return false;
	return hash_code (s1 + 1, t1 + 1) == hash_code (s2 + 1, t2 + 1);
}

int main ()
{
	//freopen ("1.txt", "r", stdin);
	scanf ("%s", s);
	len = strlen (s);
	hash_init ();

	// 去掉后导0
	while (len >= 1 && s[len - 1] == '0')
		s[--len] = '\0';	
	
	if (!len)
		printf ("0\n");
	else
	{
		// 找出最大数字
		char max_val = '0';
		for (int i = 0; i < len; i++)
			max_val = max (max_val, s[i]);

		// 将最大数字的下标放在数组a中
		vector a;
		for (int i = 0; i < len; i++)
		{
			if (s[i] == max_val)
				a.push_back (i);
		}

		int ans, index = a[0];
		for (int i = 1; i < a.size (); i++)
		{
			// 通过二分+哈希找出两个后缀的最长公共前缀
			int l = 0, r = N, m;
			while (l <= r)
			{
				m = (l + r) >> 1;
				if (C (index, a[i], m)) l = m + 1;
				else r = m - 1;
			}
			// 找到最长公共前缀r之后,就可以判断这两个后缀谁更大了
			if (s[index + r] < s[a[i] + r])
				index = a[i];
		}
		printf ("%s\n", s + index);
	}
	return 0;
}


你可能感兴趣的:(EOJ 3486 二分+哈希)