Codeforces Round #726 (Div. 2) E2. Erase and Extend (Hard Version) (贪心 or Z_Function)

本文详细介绍了Codeforces Round #726 (Div. 2) E2题目的解题思路,重点讲解了利用贪心策略和Z函数解决字符串操作问题的方法,旨在最小化最终字典序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接:https://codeforces.com/contest/1537/problem/E2

题目大意:

        有一个长度为n的初始字符串s,由小写字母组成,可以进行如下两种操作:

        1,若s的字符数大于1,可删除s末尾的字符

        2,在s的末尾加上自身,s=s+s

        可以进行无限次上述操作,我们需要得到一个长度为k的字符串,需要让该字符串的字典序最小。(1<=n,k<=5*10^5)

题解:

个人的解法:贪心

        1,若首字符为a,那么目标字符串一定为aaaaaa,我们可以首先构造出仅由s[0]构造的一个方案,寻找比它更优的方案

        2,继续推导,若s[i]>s[0],则s[i]及其后面的字符一定会被删除

        3,剩下的字符,我们首先可以确定,第一个字符后,连续的比它小的字符一定必选。例如:dabcd....,那么dabc这个前缀是必选的。继续推导,若前缀1到i-1的字符必选,且s[i]<s[0],则s[i]必选。

        4,我们如何判断接下来的字符串如何选,我们设s[i]==s[0],且前i个字符必选。设s[i:j]表示s[i]到s[j]连续的子串,那么:

        若s[0:i-1]<s[i:i+i-1],则s[i]及其之后的字符必定不选

        若s[0:i-1]==s[i:i+i+1],此时s[i]到s[i+1]的字符串必选,选择以后并不会降低结果的字典序。

        若s[0:i-1]>s[i:i+i+1],则s[i]是必选的

        最后,若s[i]后缀的长度不足,则只有在s[i]的后缀小于前面已选的字符串的情况下,s[i]才必选

注意代码实现细节,复杂度O(n)

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int nn =5100;
const int inff = 0x3fffffff;
const double eps = 1e-8;
typedef long long LL;
const double pi = acos(-1.0);
const LL mod = 1000000007;
int n,k;
string s;
int main()
{
    cin>>n>>k;
    string s;
    cin>>s;
    string pre;
    pre="";
    pre+=s[0];
    for(int i=1;i<n;i++)
    {
        if(s[i]>s[0])
            break;
        if(s[i]<s[0])
            pre+=s[i];
        else {
            int lp=pre.size();
            bool ok=true;
            int tj=-1;
            for(int j=0;j<lp;j++)
            {
                string tem;
                if(i+j<n)
                    tem=s[i+j];
                else
                    tem=s[(i+j)%n];
                if(tem[0]<s[j])
                {
                    tj=i+j;
                    break;
                } else if(tem[0]>s[j]) {
                    ok=false;
                    break;
                }
            }
            if(!ok)
            {
                break;
            } else {
                for(int j=0;j<lp&&(i+j)<n;j++)
                {
                    if(tj!=-1&&i+j>tj&&s[i+j]>=s[0])
                    {
                        break;
                    }
                    pre+=s[i+j];
                    //cout<<i<<" "<<j<<" "<<pre<<endl;
                }
                i=pre.size()-1;
            }
        }
    }
    while(pre.size()<k)
    {
        pre=pre+pre;
    }
    for(int i=0;i<k;i++)
        cout<<pre[i];
    cout<<endl;
    return 0;
}

官方解法:

        若前缀S[1:i]为当前找到的最优解,我们如何判断前缀S[1:j]是否更优呢?

        若s[j]小于由目前的最优解构成的目标串位置j的字符,则前缀s[1:j]更优

        反之则大于,则j及其后面的字符一定不会产生更优的解

        接下来是等于的情况,如何处理?

        具体来说,就是当S[1:j-i]==S[i+1:j]时,如何处理

        我们设A=S[1:j-i],B=S[j-i+1:i],则目标串有两种构造形式

        ABABABAABAABAABA

        我们需要判断ABBA谁更优
        我们可以预处理出Z_Function,然后O(1)判断

        Z_Function的定义:Z[i]表示从i开始的子串和原串最大匹配长度。可以O(n)的复杂度求出所有Z[i]。

        代码如下:

        

#include<bits/stdc++.h>
using namespace std;
const int nn =210000;
const int inff = 0x3fffffff;
const double eps = 1e-8;
typedef long long LL;
const double pi = acos(-1.0);
const LL mod = 1000000007;
vector<int> Z_Function(string &s)
{
    int ls=s.size();
    vector<int>z(ls);
    z[0]=ls;
    int l=0,r=1;
    for(int i=1;i<ls;i++)
    {
        if(i==r)
        {
            while(r<ls&&s[r-i]==s[r])
            {
                r++;
            }
            z[i]=r-i;
            l=i;
            if(i==r)
                r++;
        } else {
            if(i+z[i-l]>=r)
            {
                z[i]=r-i;
                while(r<ls&&s[z[i]]==s[r])
                {
                    z[i]++;
                    r++;
                }
                l=i;
            } else {
                z[i]=z[i-l];
            }
        }
    }
    return z;
}
int main()
{
    int n,k;
    string s;
    cin>>n>>k;
    cin>>s;
    vector<int> z=Z_Function(s);
    int cur=0;
    for(int i=1;i<n;i++)
    {
        if(s[i]<s[i%(cur+1)])
        {
            cur=i;
            continue;
        } else if(s[i]>s[i%(cur+1)]) {
            break;
        }
        if(z[cur+1]<i-cur)
        {
            if(s[cur+z[cur+1]+1]<s[z[cur+1]])
                continue;
            else
                cur=i;
            continue;
        }
        int id=i-cur;
        if(z[id]<2*cur-i+1)
        {
            if(s[id+z[id]]>s[z[id]])
            {
                cur=i;
                break;
            }
            continue;
        }
        id=cur+1;
        if(z[id]>=i-cur)
        {
            cur=i;
        } else {
            if(s[z[id]]<s[id+z[id]])
            {
                cur=i;
                break;
            }
        }
    }
    string pre;
    for(int i=0;i<=cur;i++)
        pre+=s[i];
    while(pre.size()<k)
    {
        pre=pre+pre;
    }
    for(int i=0;i<k;i++)
        cout<<pre[i];
    cout<<endl;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值