暑假集训D7 2023.7.31 补题

I. P1709 [USACO5.5] 隐藏口令 Hidden Password

给定一个字符串,找出 任意一个位置 把字符串切成两半,然后把后面那半接到前面,得到新的字符串 .

现在要你找到以这种方法可以得到最小的字符串,输出切割的位置(第 \(i\) 个字符 \(-1\) )

\(Solution:\)

循环同构

当字符串 \(S\) 中可以选定一个位置 \(i\) 满足 \(S[i\cdots n]+S[1\cdots i-1]=T\),则称 \(S\)\(T\) 循环同构

最小表示

字符串 \(S\) 的最小表示为与 \(S\) 循环同构的所有字符串中字典序最小的字符串

Oi-wiki中关于最小表示的求解

这题最开始是暴力做的,但是死活有两个点 \(T\) 掉.

后面看题解时,其实是一个类似于双指针的解法,定义两个指针 \(i\ j\) ,分别以这两个指针为两个字符串的开头,然后定义 \(k\) 表示当前这两个字符串比较第 \(k\) 个字符.那么只要 \(a[i+k]==a[j+k]\) , \(k\) 就一直 ++ 就好了.而当 \(a[i+k]>a[j+k]\) 时,则需要 \(i=i+k+1\) ,这个地方昨天晚上看的时候一直没看懂.下面画个图就很好理解了.
image

\(k=2\) 的时候发现了 \(a[i+k] > a[j+k]\) .而你想,不管 \(i\) 移动到 \(i+1\) 还是 \(i+2\) ,以它们开头的一定还会遇到 \(a[i+k^{'}] > a[j+k^{'}]\) (可以仔细思考一下).那么就让 \(i\) 移动到 \(i+k+1\) 开始重新比较.

\(j\) 的情况也是同理的.

因此上面的伪代码如下:

    i = 0 ; j = 1 ; k = 0;
    while(i < length && j < length){
        if s[i + k] == s[j + k] k++
        if s[i + k] >  s[j + k] i = i + k + 1;
        if s[i + k] <  s[j + k] j = j + k + 1;
        ……
    }

但是我们还要考虑以下几个问题

  1. \(i=j\) 时,我们的操作便无法正常的运行,因为显然的在 \(i=j\) 时,\(i+k=j+k\) ,这样的话我们的 \(k\) 就会不断的增加从而导致 \(WA\)

  2. \(i+k\)\(j+k\) 是可能越界的

  3. \(k\) 是可能大于 \(length\)

解决方法如下:

  1. \(i=j\)时,\(j++\)

  2. 只要将 \(i+k\)\(j+k \operatorname{Mod} length\)即可

  3. 限制 \(k < length\) ,在 \(k = length\) 直接返回 \(i\)\(j\) 中位置较前的即可.因为最后一定有 \(i\) 或者 \(j\) 的其中一个先出界,未出界的那个是小的.而当 \(k = length\) 时,说明这个字符串是只由一种字符构成的,可以自己试一下.

#include<stdio.h>
#include<string.h>
#include<iostream>
#define endl '\n'
using namespace std;

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int n;
	cin>>n;
	string s;
	string t;
	while(cin>>t)
	{
		s+=t;
	}
	int i = 0,j=1,k = 0;
	int res = 0;
	while(i<n&&j<n)
	{
		k = 0;
		while(k<n&&s[(i+k)%n]==s[(j+k)%n])k++;
		if(k==n)
		{
			//cout<<"equal"<<endl;
			break;
		}
		if(s[(i+k)%n]>s[(j+k)%n])
		{
			i = i+k+1;
		}
		if(s[(i+k)%n]<s[(j+k)%n])
		{
			j = j+k+1;
		}
		if(i==j)j++;
	}
	res = min(i,j);
	cout<<res<<endl;
	return 0;
}

posted @ 2023-07-31 21:18  LZH_03  阅读(5)  评论(0编辑  收藏  举报