分析:如果单纯只用排序的思维来解这道题的话,无论什么排序,他的时间复杂的都不能完全满足题目要求。所以需要考虑优化操作步骤。就是尽量减少升序和降序的操作。首先,当重复遇到降序操作时,那么只有这两次降序中的最大降序范围有效,比如对123456从1到3降序与1到4降序操作,两次操作后其实只有1到4有效,同样的道理两次连续的升序操作也只有范围大的有效。因此在遇到连续的0或者1时就需要将两次操作进行合并,例如
0 | 5 |
1 | 6 |
0 | 4 |
0 | 6 |
1 | 3 |
1 | 7 |
0 | 7 |
左边的操作可以简化为右边的操作,即将连续的操作进行合并,保留范围大的操作。
0 | 5 |
1 | 6 |
0 | 6 |
1 | 3 |
0 | 7 |
现在的操作就变成了010101类型,就是先降序再升序,由于序列本身一开始就是升序,所以第一次必为降序才有效,否则无效, 所以操作顺序一定是 01010101类型的。
那么可以把01看着一次循环,经过一次01操作,最左边一定是降序,最右边一定是升序。
如果下一次0操作范围比上一次0操作范围大,那这次的降序操作就使得前两次的操作相当于没做。
比如 1 2 3 4 5 6 7 8
0 4操作:4 3 2 1 5 6 7 8
1 3: 4 3 1 2 5 6 7 8
再进行 0 5,这次操作范围比上一次相同操作0 4的范围要大:5 4 3 2 1 6 7 8,就相当于未进行前两步操作,直接就进行的 0 5操作。因此对于0操作的范围必须逐渐减小。
同理 进行010后,再进行1操作,这个操作范围一定比上次1操作范围大,否则前两次都会无效。
比如 1 2 3 4 5 6 7 8
0 6:6 5 4 3 2 1 7 8
1 4:6 5 4 1 2 3 7 8
0 5:6 5 4 2 1 3 7 8
1 3:6 5 1 2 3 4 7 8
1 3是从3到末尾的升序比上一次1 4范围大,所以就相当于前两次没有操作,他就等效于只进行了 0 6和1 3操作。
综上所述,要简化操作,就得分两步,第一步合并连续的操作,第二步判断当前操作范围是否大于前一次相同操作范围,是的话就前两次操作无效。
那么具体代码上我们可以用栈的思维,进栈和出栈都再栈顶进行操作,维护两个栈p和q,只要p,q同进同出,就能让两个数在栈的同一高度。
如果前一次操作与当前操作相同,就合并当前操作,在进行判断当前操作与上一次相同的操作范围的大小,如果大,就一直连续出栈两次,并并不断循环,直到比前一次范围小。
经过以上操作,我们就能得到最简化的操作p q,他们大概就是0操作的q值逐渐减小,1操作的q值逐渐增大。大概如下图
此时对这样简化了的操作直接排序依然不能直接满足时间复杂度,所以还有对排序进行优化 。
经过一轮01操作后,右边降序左边升序。那么我们可以先把两端确定了。
比如1 2 3 4 5 6 7 8
经过 一轮0 6 和1 3操作后就变成了 6 5 1 2 3 4 7 8。我们发现这个操作后在第3到第6位是1 2 3 4连续递增的。对他再进行一轮 0 5 ,1 4操作:6 5 321478 6 5 3 1 2 4 7 8,在第4位和第5位是连续的。发现最开始的 65 位置没变,4 7 8也位置也没变,实质上可以通过图也能看出来,每次改变的都是上一次重合的部分。未重合部分的顺序没有改变。因此我们可以每一次都可以把未重合部分的数字给确定下来。具体步骤见代码。
#include<iostream>
#include<vector>
using namespace std;
int max(int a, int b)
{
if (a > b)
return a;
return b;
}
int min(int a, int b)
{
if (a < b)
return a;
return b;
}
int main()
{
int n, m;
cin >> n >> m;
vector<int>arr;
vector<int>p;
vector<int>q;//我用容器代替栈,方便访问数据。
int a, b;//用来输入当前操作的p q值
for (int i = 0; i < n; i++)
{
arr.push_back(i + 1);
}
while(m--)
{
cin >> a >> b;
if (a == 0)//当前操作为0的情况
{
if (p.size() == 0)//如果栈中没有数,就直接将当前操作入栈。
{
p.push_back(a);
q.push_back(b);
}
else
{
if (p.back()== 0)//上一次操作也为0即两次连续的00
{
b = max(q.back(), b);//合并上一次操作到当前操作,q值越大范围越大。
p.pop_back();//上一次操作已经合并到当前操作,所以将上一次操作出栈。
q.pop_back();
}
while (p.size() >=2 && q[q.size() - 2]<=b)//判断当前操作范围是否比上一次操作范围大,是的话连续出栈两次。010的情况
{
p.pop_back();
p.pop_back();
q.pop_back();
q.pop_back();
}
p.push_back(a);
q.push_back(b);
}
}
else
{
if (p.size() == 0)//第一操作为1时不存入栈。
{
continue;
}
if (p.back() == 1)//11的情况,合并当前操作
{
b=min(q.back(), b);
p.pop_back();
q.pop_back();
}
while (p.size() >=2 && q[q.size() - 2] >= b)//101的情况,判断当前操作是否比上一次相同操作范围大。是的话出栈两次并不断循环。
{
p.pop_back();
p.pop_back();
q.pop_back();
q.pop_back();
}
p.push_back(a);
q.push_back(b);
}
}
int r_pos = n - 1, l_pos = 0;//定义左右端点
for (int i = 0; i < p.size(); i++)
{
if (p[i] == 0)//0操作的情况
{
while (r_pos > q[i]-1) { //右边是升序,从右边位置到q相当于从n到q逐渐减小 ,n是从最大值开始赋值,赋值一个n少一个所以n不断减少。
arr[r_pos] = n;
r_pos--;
n--;
if (l_pos > r_pos)
{
break;
}
}
}
else {
while (l_pos < q[i]-1) {//左边是降序从左边到q也相当于逐渐减小。
arr[l_pos] = n;
l_pos++;
n--;
if (l_pos > r_pos)
{
break;
}
}
}
if (l_pos > r_pos) {
break;
}
}
if (p.back()==0) { //最后次是降序的情况
while (l_pos <= r_pos) {//如果左右位置还没重合,说明中间还有位置没有确定,经过推算中间位置是从左到右逐渐降序
arr[l_pos] = n;
l_pos++;
n--;
}
}
else {
while (l_pos <= r_pos) {//这种情况下是从右到左逐渐降序
arr[r_pos] =n;
r_pos--;
n--;
}
}
for (int i = 0; i < arr.size(); i++)
{
cout << arr[i] << " ";
}
}