计蒜客- 2019计蒜之道D

计蒜客- 2019计蒜之道D. “星云系统”(单调队列/单调栈)

题意:

​ 现在给定你一个字符串 s s s 以及一个整数 k k k,请求出 s s s的字典序最小的长度为 k k k的子序列。

数据范围:

0 &lt; k ≤ ∣ s ∣ ≤ 5000000 0&lt;k≤∣s∣≤5000000 0<ks5000000

样例输入:

helloworld
5

样例输出:

ellld

思路:

假如我们先不考虑长度为k的限制我们应当怎么做?

​ 我们以样例为例子。从左到右扫描字符串一遍:h、然后e,因为e的字典序比h小所以h不应该在e前面,所以把h删去,此时待选序列只有e;然后扫描l、l、o、r,此时待选序列中有ellor;然后扫描l,因为r比l大,所以r不应该在l之前,把r删除,同样o也是如此,现在待选序列就只剩elll;然后扫描d。按照前面的原理,所以elll都删去,现在待选序列中只有d。

不过前面的部分是否有些似曾相识,这不是符合单调栈的性质吗。所以前面的问题我们可以用单调栈来解决。

那么现在有了长度为k的限制,我们应该怎么做?

首先我们发现,假如字符串长度为n,那么想要答案最优就要从第1~(n-k+1)中取一个最小的字母。这个就相当于从单调栈中取栈底的元素,并删去(这个位置以及这个位置之前的都不能用了,但因为这个位置之前的在单调栈中不存在,故只删除这一个即可)。接下来取第二个字符,那么就是将第(n-k+2)加入单调栈,然后从单调栈中栈底元素并从单调栈删去。从单调栈中取栈底元素其实就是单调队列。

代码:

#include<bits/stdc++.h>
#define mset(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
char s[5000000+10],st[5000000+10],ans[5000000+10];
int main()
{
    int k,p,top,ls;
    scanf("%s%d",s+1,&k);
    p=0,top=0;//p指向st队头
    ls=strlen(s+1);
    int w=ls-k+1;
    for(int i=1;i<=w;++i){//初始化,先找出(1~n-k+1)中字典序最小的字符串
        while(top>0&&s[i]<st[top-1]) top--;
        st[top++]=s[i];
    }
    int cnt=0;
    ans[cnt++]=st[p++];//取出栈底字符作为答案的第一个字符。
    for(int i=w+1;i<=ls;++i){//取剩下的k-1个字符
        while(top>p&&s[i]<st[top-1]) top--;
        st[top++]=s[i];
        ans[cnt++]=st[p++];
    }
    ans[cnt]='\0';
    printf("%s\n",ans);
    return 0;
}

单调队列用deque实现:

#include<bits/stdc++.h>
#define mset(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
string s,ans;
deque<char> st;
int main()
{
    int k;
    ios::sync_with_stdio(false);cin.tie(0);
    cin>>s>>k;
    int w=s.length()-k;
    for(int i=0;i<=w;++i)
    {
        while(!st.empty()&&s[i]<st.back()) st.pop_back();
        st.push_back(s[i]);
    }
    ans+=st.front();
    st.pop_front();
    for(int i=w+1;i<s.length();++i){
        while(!st.empty()&&s[i]<st.back()) st.pop_back();
        st.push_back(s[i]);
        ans+=st.front();
        st.pop_front();
    }
    cout<<ans<<endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值