目录
题目1 : Smallest Substring
时间限制:10000ms
单点时限:1000ms
内存限制:256MB
描述
Given a string S and an integer K, your task is to find the lexicographically smallest string T which satisfies:
1. T is a subsequence of S
2. The length of T is K.
输入
The first line contain an integer K. (1 <= K <= 100000)
The second line contains a string of lowercase letters. The length of S is no more than 100000.
输出
The string T.
样例输入
4
cacbbac
样例输出
abac
题意分析:
1.题是什么?
给你一个十万长度的字符串s,和十万以内的数字k,问你长度为k的字典序最小的s的子序列是什么?
2.思路
(1).大方向
先定义一下,ans[i]表示ans中第i个字符,slen表示原串长度,设l=ans[i-1]来源自s中的位置+1,r=slen-(k-i),ans[i]应该是选自原串s中 [l,r] 之间的最靠前的字典序最小的字符
左边界很容易理解,ans中第i个字符自然是选自第i-1个字符所选位置之后(代码中spos就是动态记录的第i-1个字符所选位置)
右边界是为了保证选完第i个后剩下的可选字符个数至少有k-i个,一定能凑够k个长度的答案.毕竟字典序比较是一种从左到右的贪心比较,只要这个足够小,后面的是什么无所谓,只要有那么长就够了.
(2).我的思路
我这里第一次实现是以暴力区间搜索完成的,实际最劣复杂度其实是n*n,重复扫描了太多,故而只能过90%的数据
//tle代码
const int maxn=1e5+5;
void solve(){
int k;
char s[maxn];
char ans[maxn];
scanf("%d%s",&k,&s);
int slen=strlen(s);
int anspos=0,spos=0;
while(anspos<k){
char temp='z'+1;
for(int i=spos;i<=slen-k+anspos;i++){
if(s[i]<temp){
temp=s[i];
spos=i+1;
}
}
ans[anspos++]=temp;
}
ans[anspos]='\0';
printf("%s\n",ans);
}
这后面我通过预处理每个字符的位置,存进vector,然后在确定ans[i]是什么时,就从'a'到'z'扫描哪个字符所对应的vector中先出现符合条件的位置.实际实现我是用数组模拟vector纯c语言实现.效率上分析其实复杂度最劣为26*maxk+maxslen,虽然名为O(n)复杂度,可是与官方提供的O(nlogn)方法相比应该更慢..毕竟logn还是很快
(3).官方思路
官方分析:http://hihocoder.com/discuss/question/5616
官方思路主要是想通过维持一个小根堆或者以set平衡二叉树的性质来实现对(字符,位置)键值对的排序,以logn完成对每个无效字符的删除.并选取k个有效的最小的.
(4).RMQ思路
其实看完大方向之后就该想到一个经典问题,RMQ区间最小值问题,不过这里的'最小'需要重新定义,这里可以以 线段树做的RMQ 做实现,
3.ac代码
(1).我的思路
#include <stdio.h>
#define maxcharnum 26
const int maxn=1e5+5;
const int inf=1e8;
char s[maxn];
char ans[maxn];
//模拟vector<int> pos[maxcharnum];
int pos[maxcharnum][maxn];
int size[maxcharnum];
void solve(){
int k;
scanf("%d%s",&k,&s);
//初始化'vector'
for(int i=0;i<maxcharnum;i++) size[i]=0;
//预处理每种字符的位置进'vector',顺手把字符串长度slen做好了
int slen=0;
while(s[slen]){
int temp=s[slen]-'a';
pos[temp][size[temp]++]=slen;
slen++;
}
//inf做收尾限制,必不可少
for(int i=0;i<maxcharnum;i++) pos[i][size[i]++]=inf;
int index[maxcharnum];
for(int i=0;i<maxcharnum;i++) index[i]=0;
int anspos=0,spos=0;//spos记录ans[i-1]选取自s中的位置+1
while(anspos<k){
for(int i=0;i<maxcharnum;i++){
while(pos[i][index[i]]<spos) index[i]++;//删除无效的位置,这就是+maxslen的来源
if(pos[i][index[i]]>=spos&&pos[i][index[i]]<=slen-(k-anspos)){
spos=pos[i][index[i]++]+1;
ans[anspos]='a'+i;
break;
}
}
anspos++;
}
ans[anspos]='\0';//手动结尾字符串
printf("%s\n",ans);
}
int main(){
solve();
return 0;
}