暑假集训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\) 循环同构的所有字符串中字典序最小的字符串
这题最开始是暴力做的,但是死活有两个点 \(T\) 掉.
后面看题解时,其实是一个类似于双指针的解法,定义两个指针 \(i\ j\) ,分别以这两个指针为两个字符串的开头,然后定义 \(k\) 表示当前这两个字符串比较第 \(k\) 个字符.那么只要 \(a[i+k]==a[j+k]\) , \(k\) 就一直 ++ 就好了.而当 \(a[i+k]>a[j+k]\) 时,则需要 \(i=i+k+1\) ,这个地方昨天晚上看的时候一直没看懂.下面画个图就很好理解了.
当 \(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;
……
}
但是我们还要考虑以下几个问题
-
当 \(i=j\) 时,我们的操作便无法正常的运行,因为显然的在 \(i=j\) 时,\(i+k=j+k\) ,这样的话我们的 \(k\) 就会不断的增加从而导致 \(WA\)
-
\(i+k\) 和 \(j+k\) 是可能越界的
-
\(k\) 是可能大于 \(length\) 的
解决方法如下:
-
当 \(i=j\)时,\(j++\)
-
只要将 \(i+k\) 和 \(j+k \operatorname{Mod} length\)即可
-
限制 \(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;
}