习题:JM的西伯利亚特快专递 (线段树)

题目

今天JM收到了一份来自西伯利亚的特快专递,里面装了一个字符串 \(s\) ,仅包含小写英文字母。于是JM决定跟qz和寒域爷一起玩一个游戏:

JM手里拿着字符串 \(s\) ,qz手里拿着字符串 \(t\) ,寒域爷手里拿着字符串 \(u\) ,初始时 \(t\)\(u\) 均为空。有两种操作:

  1. 将字符串 \(s\) 的首部字符取出,插入字符串 \(t\) 的尾部。

  2. 将字符串 \(t\) 的尾部字符取出,插入字符串 \(u\) 的尾部。

JM,qz和寒域爷随意地进行上述两种操作,直到字符串 \(s\)\(t\) 均为空时才停止。现在JM想知道,他们能得到的字典序最小的字符串 \(u\) 是什么。

注意,对于一个字符串 \(s\) ,其长度为 \(n\) ,其中字符的下标为 \(1, 2, ..., n\) ,则我们定义 \(s\) 的首部字符为 \(s_1\) ,尾部字符为 \(s_n\)

思路

还算简单的一道题,你直接考虑字符串u的性质就行了,用线段树维护

代码

#include<iostream>
#include<cstring>
#include<deque>
#include<cstdio>
using namespace std;
struct node
{
	char c;
	int l;
	int r;
}tre[400005];
char minn;
char s[100005];
int now;
int lens;
deque<char> t;
deque<char> u;
void build(int l,int r,int k)
{
	tre[k].l=l;
	tre[k].r=r;
	if(l==r)
		return;
	int mid=(l+r)>>1;
	build(l,mid,k<<1);
	build(mid+1,r,k<<1|1);
}
void change(int pos,int k,char c)
{
	if(tre[k].l>pos||tre[k].r<pos)
		return;
	if(tre[k].l==tre[k].r)
	{
		tre[k].c=c;
		return;
	}
	change(pos,k<<1,c);
	change(pos,k<<1|1,c);
	tre[k].c=min(tre[k<<1].c,tre[k<<1|1].c);
}
char ask(int l,int r,int k)
{
	if(tre[k].l>r||tre[k].r<l)
		return 'z';
	if(l<=tre[k].l&&tre[k].r<=r)
		return tre[k].c;
	if(tre[k].l==tre[k].r)
		return tre[k].c;
	return min(ask(l,r,k<<1),ask(l,r,k<<1|1));
}
int main()
{
	ios::sync_with_stdio(false);
	cin>>s;
	lens=strlen(s);
	build(1,lens,1);
	for(int i=lens;i>=1;i--)
	{
		s[i]=s[i-1];
		change(i,1,s[i]);
	}
	now=0;
	while(now!=lens)
	{
		minn=ask(now+1,lens,1);
		while(!t.empty())
		{
			if(minn>=t.back())
			{
				u.push_back(t.back());
				t.pop_back();
			}
			else
				break;
		}
		for(int i=now+1;i<=lens;i++)
		{
			if(s[i]!=minn)
				t.push_back(s[i]);
			else
			{
				now=i;
				u.push_back(s[i]);
				break;
			}
		}
	}
	while(!u.empty())
	{
		cout<<u.front();
		u.pop_front();
	}
	while(!t.empty())
	{
		cout<<t.back();
		t.pop_back();
	}
	return 0;
}
posted @ 2019-12-21 14:19  loney_s  阅读(273)  评论(0)    收藏  举报