【CF】1779D-Boris and His Amazing Haircut 题解

传送门: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;//清空标记数组 
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值