2020-08-09

神奇项链

母亲节就要到了,小 H 准备送给她一个特殊的项链。这个项链可以看作一个用小写字母组成的字符串,每个小写
字母表示一种颜色。为了制作这个项链,小 H 购买了两个机器。第一个机器可以生成所有形式的回文串,第二个
机器可以把两个回文串连接起来,而且第二个机器还有一个特殊的性质:假如一个字符串的后缀和一个字符串的前
缀是完全相同的,那么可以将这个重复部分重叠。例如:aba和aca连接起来,可以生成串abaaca或 abaca。现在给
出目标项链的样式,询问你需要使用第二个机器多少次才能生成这个特殊的项链。

Input

输入数据有多行,每行一个字符串,表示目标项链的样式。
每个测试数据,输入不超过 5行
每行的字符串长度小于等于 50000

Output

多行,每行一个答案表示最少需要使用第二个机器的次数。

Sample Input
abcdcba
abacada
abcdef

Sample Output
0
2
5

这道题的思路比较明确,先使用马拉车算法,接着将问题转化一个线段覆盖问题。

在此之前,我们先看一道比较基础的线段覆盖问题

P1803 凌乱的yyy / 线段覆盖
题目背景
快 noip 了,yy 很紧张!

题目描述
现在各大 oj 上有 nn 个比赛,每个比赛的开始、结束的时间点是知道的。

yy 认为,参加越多的比赛,noip 就能考的越好(假的)。

所以,他想知道他最多能参加几个比赛。

由于 yy 是蒟蒻,如果要参加一个比赛必须善始善终,而且不能同时参加 2 个及以上的比赛。

输入格式
第一行是一个整数 n ,接下来 nn 行每行是 2 个整数 a[i],b[i]表示比赛开始、结束的时间。

输出格式
一个整数最多参加的比赛数目。

输入输出样例
输入
3
0 2
2 4
1 3
输出
2

本题比较简单,思路就是先将时间按照比赛结束时间的先后顺序排好序,然后再使用贪心的方法,依次将下一个排在其结束时间之后的比赛安排到后面即可。
关于这点的深究我看到过一篇非常好的题解,下面引用一下:

在一个数轴上有n条线段,现要选取其中k条线段使得这k条线段两两没有重合部分,问最大的k为多少。

最左边的线段放什么最好?

显然放右端点最靠左的线段最好,从左向右放,右端点越小妨碍越少

其他线段放置按右端点排序,贪心放置线段,即能放就放
下面上代码

#include<bits/stdc++.h>
struct px{
int a;
int b;
}x[200000];
bool cmp(px x,px y)
{
    return x.b<y.b;
}

using namespace std;
int main()
{
    int n,sum=1,mi;
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>x[i].a>>x[i].b;
    sort(x+1,x+n+1,cmp);
    mi=x[1].b;
    int j=1;
    while(j<=n)
    {
        j++;
        if(x[j].a>=mi)
        {
            sum++;
            mi=x[j].b;
        }
    }
    cout<<sum;
    return 0;
}

下面直接上本题的题解了,在使用了马拉车算法之后,基本思想是类似的,算出每个中心点的左边界和右边界都分别是什么,对应了线段覆盖问题的左端点和右端点,但是这里要求的是下一个的左端点要在上一个的右端点的范围之内,这与上一道给出的模板题是不同的,但基本思想是一样的。
下面上代码:

#include<bits/stdc++.h>
using namespace std;
#define N 10000005

char a[N],s[N];
int la,n,mx,id,cnt,now,r,ans,p[N];
struct hp{int l,r;}st[N];

void clear()//每次搞一个整体初始化,会方便很多
{
    memset(s,0,sizeof(s));memset(p,0,sizeof(p));
    la=n=mx=id=cnt=now=r=ans=0;
}
int cmp(hp a,hp b)
{
    return a.l<b.l||(a.l==b.l&&a.r>b.r);//这里排序是按照左边优先的原则,如果左边相同就要看右边谁的覆盖半径大
}
int main()
{
    while (~scanf("%s",a))//可以一直循环读入字符串
    {
        clear();
        la=strlen(a);s[0]='*';
        for (int i=0;i<la;++i) s[++n]='#',s[++n]=a[i];
        s[++n]='#';
        for (int i=1;i<=n;++i)
        {
            if (mx>i) p[i]=min(p[2*id-i],mx-i);
            else p[i]=1;
            while (s[i-p[i]]==s[i+p[i]]) ++p[i];
            if (i+p[i]>mx)
            {
                mx=i+p[i];
                id=i;
            }
        }//使用马拉车算法,对每个点当做中心点所对应对称半径进行标记
        for (int i=1;i<=n;++i) st[++cnt].l=i-p[i]+1,st[cnt].r=i+p[i]-1;//这一步就是在计算每个中心点对应的左右端点,及每条线段的左右端点
        sort(st+1,st+cnt+1,cmp);
        r=st[1].r;
        ans=1;
        now=2;
        while (r<n)
        {
            mx=r;
            while (now<=cnt&&st[now].l<=r)
            {
                mx=max(mx,st[now].r);
                ++now;
            }
            ++ans;
            r=mx;//这几步的思路与上一题基本相同
        }
        printf("%d\n",ans-1);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值