最小表示法+uva719

首先讲一下字符串的循环表示,下面的内容转自:最小表示法


最小表示法”比起动态规划、贪心等思想,在当今竞赛中似乎并不是很常见。但是在解决判断“同构”一类问题中却起着重要的作用。

本文即将讨论字符串中的同构问题,如何巧妙地运用最小表示法来解题呢,让我们继续一起思考吧。

到底什么是循环同构的字符串呢?

直接举个例子:

比如 str1 = "abdea"  而字符串str2是str1从第i(i>0)个开始将str1从i循环到末尾再从头开始循环到i,那么这两个字符串就是循环同构的.比如str2 = "deaab";

那么什么是字符串的最小表示呢?

比如上面那个str1,就是从str1的同构字符串中字典序最小的,那么str1的最小表示就是"aabde".

如何得到一个字符串的最小表示?

 

设函数 M(s) 返回值意义为:

s 的第 M(s) 个字符引起的 s 的一个循环表示是 s 的最小表示。

若有多个值,则返回最小的一个

比如M("bbbaab") =  4; 也就是最小表示为aabbbb,是从字符串"bbbaab"的第4个的字符开始的同构字符串。

现在换一种思路:

设有字符串s1,s2

u=s1+s1(也就是将s1复制一份到s1后面) w=s2+s2 并设指针 i,j 指向 u,w 第一个字符

如果 s1 s2 是循环同构的,那么当 i,j 分别指向 M(s1),M(s2) 时,一定可以得到 u[ i→i +|s1|-1]=w[ j→j +|s2|-1] ,迅速输出正确解。

同样 s1 s2 循环同构时,当 i,j 分别满足

i M (s1), j M (s2) 时,

两指针仍有机会达到 i =M(s1),j=M(s2) 这个状态。

问题转化成,两指针分别向后滑动比较,如果比较失败,如何正确的滑动指针,新指针 i ’,j’ 仍然满足

i ’≤M(s1), j’≤M (s2)

设指针 i,j 分别向后滑动 k 个位置后比较失败 (k 0) ,即有

u[ i+k ] w[ j+k ]

u[ i+k ]>w[ j+k ] ,同理可以讨论 u[ i+k ]<w[ j+k ] 的情况。

因为 u[x] u[ i ] (x- i ) 个位置,

对应的可以找到在 w[j] (x- i ) 个位置的 w[j+(x- i )]

同样对应的有 u[x+1] w[j+(x+1-i)] u[x+2] w[j+(x+2)- i ]

直到 u[i+k-1] w[j+k-1]

它们都是相等的,

即有 u[x→i+k-1]=w[j+(x- i )→j+k-1]

很容易就得到 u[ x→i+k ]>w[j+(x- i )→ j+k ]

所以 s1 (x-1) 不可能是 s1 的最小表示!

因此 M(s1)> i+k

指针 i 滑到 u[i+k+1] 处仍可以保证小于等于 M(s1)

同理,当 u[ i+k ]<w[ j+k ] 的时候,可以将指针 j 滑到 w[j+k+1] 处!

也就是说,两指针向后滑动比较失败以后,

指向较大字符的指针向后滑动 k+1 个位置。

举例:

s1=‘ babba s2=‘ bbaba 。(两个同构字符 串)

u=s1+s1='babbababba'  w=s2+s2='bbababbaba'

初始化i=0,j=0,k=0;(i作为u的指针,j作为w的指针)              

比较失败时 k=1 (u[i+k]!=w [j+k]

由于u[i+k]<w[j+k]  所以j=j+k+1;

j = 2 i不变(i=0); k=0

继续比较发现当k=0时u[i+k]>w[j+k],所以i=i+k+1;

i=1,j不变(j=2);k=0;

继续比较,发现当k=2时u[i+k]>w[j+k]所以i = i+k+1;

i =  4j不变(j=2)k=0;

继续比较,这个时候就找到了最小表示ababb

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<vector>
#include<cmath>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<algorithm>
using namespace std;
const int maxn=10010;
char s[maxn];
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%s",s);
        int p=0,q=1;//取两个同构的字符串一个从下标0开始,一个从下标1开始
        int n=strlen(s);
        while(p<n&&q<n) 
        {
            int k=0;
            for(k=0;k<n;k++)
                if(s[(p+k)%n]!=s[(q+k)%n])break;
            if(k==n)break;
            if(s[(p+k)%n]>s[(q+k)%n])p+=k+1;
            else q+=k+1;
            if(p==q)q++;
        }
        printf("%d\n",min(q,p)+1);
    }
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值