CodeForces 631C Report「思维题」
题目描述
题意翻译
给定 \(n\) 个整数组成的序列和 \(m\) 个操作,每个操作给定 \(t_i\) 和 \(r_i\),操作分为两种:
- \(t_i\) 为 \(1\),则将前 \(r_i\) 个数按照连续不减序(从小到大)排列;
- \(t_i\) 为 \(2\),则将前 \(r_i\) 个数按照连续不增序(从大到小)排列。
求 \(m\) 次操作过后的序列。
输入输出样例
输入 #1
3 1
1 2 3
2 2
输出 #1
2 1 3
输入 #2
4 2
1 2 4 3
2 3
1 2
输出 #2
2 4 1 3
思路分析
疫情期间把这题水过了,突然想回来补锅
- 题面翻译的非常简洁明了又准确。
不知道有多少人像我一样只优化了一丢丢就开始sort - 然后应该基本上都会想到这个很显然的性质:如果前面的修改操作的右端点小于后面操作的右端点,那么这次操作其实就是无效的,因为会被覆盖掉
- 所以就很容易挑选出有效的操作次数,而被筛选操作是这样的:编号越小的修改所覆盖的区间越大,也就是覆盖区间的长度是单调递减的。如果不满足单调递减,那么前面一定会有操作会被覆盖。
- 那么这个性质怎么用?其实上面说了,就是因为
sort
太多了所以 \(TLE\) 掉了,而这时候在这个性质下,完全不必sort
,而是可以直接赋值的。方法就是先排序,然后两次被挑选出的操作的端点之间直接赋值。在上面的性质的下,这样的正确性是有保证的
详见代码
\(Code\)
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define R register
#define N 200010
using namespace std;
inline int read(){
int x = 0,f = 1;
char ch = getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
int n,m,a[N],c[N],flag[N];
struct data{
int opt,pos;
}b[N];
inline bool cmp(int x,int y){return x>y;}
int main(){
n = read(),m = read();
for(R int i = 1;i <= n;i++)a[i] = read();
for(R int i = 1;i <= m;i++){
b[i].opt = read(),b[i].pos = read();
}
int mx = 0;
for(R int i = m;i;i--){//倒序处理出每次操作的后面的操作覆盖的最大区间
flag[i] = mx;
mx = max(mx,b[i].pos);
}
for(R int i = 1;i <= mx;i++)c[i] = a[i];//c数组用于下面原数组的赋值
sort(c+1,c+1+mx,cmp);
int l = 1,r = mx;
for(R int i = 1;i <= m;i++){
if(flag[i]>=b[i].pos)continue;
if(b[i].opt==1){
for(R int j = 0;b[i].pos-j>flag[i];j++){//从右端点开始赋值,一直到下一次操作的端点
a[b[i].pos-j] = c[l++];//从大的开始拿
}
}else{
for(R int j = 0;b[i].pos-j>flag[i];j++){
a[b[i].pos-j] = c[r--];//从小的开始拿
}
}
}
for(R int i = 1;i <= n;i++)printf("%d ",a[i]);
return 0;
}