传送门:1779D
标签:数据结构
题目大意
一个人去理发,已知他的头有n个位置,位置i(1<=i<=n)的头发长度为ai,他希望理发后位置i的头发长度为ci。理发师有m个理发刀,每个理发刀有一个属性x,理发刀j能对任意区间[L,R]进行操作,使得所有i(L<=i<=R)的ai=min(ai,xj),每个理发刀最多只能使用一次。问:能否满足他对头发长度的要求。
输入:T(测试组数),n(3<=n<=2e5),ai,ci,m(1<=m<=2e5),xi。
输出:能满足要求则输出"YES",否则输出"NO"。
算法分析
- 首先我们要理解题意。理发刀能将一段连续的头发剪短,但无论如何都不可能让头发比原来更长。所以我们可以得到两个结论:1、如果出现ai<ci的情况,必然不可能满足要求,我们可以直接输出"NO"。2、对一个位置i使用的所有理发刀必须满足xi>=ci,且至少要有一个理发刀满足xi=ci。从区间选择的角度来说,就是对于理发刀j,其理发区间[L,R]必须满足:对任何i(L<=i<=R),有ci<=xi。
- 在同一位置多次使用理发刀,只有x最小的一次是有效的,而在满足题目要求的情况下,这个最小的x就是ci。那么我们又可以得到两个结论:1、只有x的值在数组c中出现过的理发刀才会被使用。2、c大的位置使用理发刀时选的区间只能包括c小的位置。具体地说,我们要从1到n遍历i,如果这个点已经满足要求则continue,否则选择一个x==ci的理发刀,以i为L向右扩展区间。为什么要向右扩展呢?因为遍历是从左向右进行的,i左边的点必然都能满足要求,否则就会直接输出"NO"并结束循环。那么我们只需要建立一个单调栈,预处理后就能O(1)查询当前位置对应的R。
- 如果我们用单调栈处理每个位置的R,也就是比ci大的第一个位置,在最坏的情况下时间复杂度还是O(n2)。因为我们不能确定在区间[L,R]中有几个数的c等于x,只能遍历整个区间,这样做显然TLE。但这不代表我们的思路错误,只能说明算法过于朴素。既然我们要找c==x的位置,不如直接让单调栈处理右边第一个大于或等于ci的c的位置,并存在数组r中。然后在遍历到i的时候,我们只要判断ri的c是否等于ci。若是,则标记位置ri为满足条件,然后令j=ri,判断rj的c是否等于ci······如此反复直到rj>n或rj的c大于ci。
代码实现
#include <iostream>
using namespace std;
#include <map>
map<int,int> mp;
int a[200001],c[200001],stack[200001],r[200001];//存右边界
bool b[200001];
int main(){
int i,j,top,x,n,m,T;
cin>>T;
while(T--){
cin>>n;
top=-1; //清空单调栈
mp.clear(); //清空理发刀map
for(i=0;i<n;i++)cin>>a[i]; //头发初始长度
for(i=0;i<n;i++)cin>>c[i]; //头发目标长度
cin>>m;
for(i=0;i<m;i++){
cin>>x;
mp[x]++; //记录每种理发刀的数量
}
for(i=0;i<n;i++)
if(c[i]>a[i]){ //头发不可能变长
cout<<"NO\n";
goto abc;
}
else if(a[i]==c[i])b[i]=1; //标记为满足条件
for(i=0;i<n;i++){
while(top>=0&&c[stack[top]]<=c[i])
r[stack[top--]]=i; //单调栈
stack[++top]=i;
}
while(top>=0)
r[stack[top--]]=n;//单调栈没处理的位置的右边界为n
for(i=0;i<n;i++){
if(b[i])continue;
if(!mp[c[i]]){ //没有相应的理发刀
cout<<"NO\n";
goto abc;
}
mp[c[i]]--;
j=r[i];
while(j<n){ //不断向右扩展区间
if(c[j]>c[i])break;
b[j]=1;
j=r[j];
}
}
cout<<"YES\n";
abc:;
for(i=0;i<n;i++)b[i]=0;//清空标记数组
}
}