NOIP模拟赛(2019.8.16)

旅馆

一、题目

OIER们最近的旅游计划,是到长春净月潭,享受那里的湖光山色,以及明媚的阳光。你作为整个旅游的策划者和负责人,选择在潭边的一家著名的旅馆住宿。这个巨大的旅馆一共有N (1 <= N <= 50000)间客房,它们在同一层楼中顺次一字排开,在任何一个房间里,只需要拉开窗帘,就能见到波光粼粼的潭面。 所有的旅游者,都是一批批地来到旅馆的服务台,希望能订到Di (1 <= Di <= N)间连续的房间。服务台的接待工作也很简单:如果存在r满足编号为r…r+Di-1的房间均空着,他就将这一批顾客安排到这些房间入住;如果没有满足条件的r,他会道歉说没有足够的空房间,请顾客们另找一家宾馆。如果有多个满足条件的r,服务员会选择其中最小的一个。 旅馆中的退房服务也是批量进行的。每一个退房请求由2个数字Xi、Di描述,表示编号为Xi…Xi+Di-1 (1 <= Xi <= N-Di+1)房间中的客人全部离开。退房前,请求退掉的房间中的一些,甚至是所有,可能本来就无人入住。 你的工作,就是写一个程序,帮服务员为旅客安排房间。你的程序一共需要处理M (1 <= M <=50000)个按输入次序到来的住店或退房的请求。第一个请求到来前,旅店中所有房间都是空闲的。

二、解法

线段树裸题,不用多讲,上代码吧。

#include <cstdio>
const int MAXN = 50005;
int read()
{
	int x=0,flag=1;char c;
	while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
	while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return x*flag;
}
int n,m;
int max(int x,int y) {
	if(x<y) return y;
	return x;
}
struct node
{
	int l,r,fr,ba,mi,loc,Max,lazy;
	node operator + (const node &x) const  {
		return node{l,x.r,fr+(fr==r-l+1)*x.fr,x.ba+(x.ba==x.r-x.l+1)*ba,ba+x.fr,r-ba+1,max(max(Max,x.Max),ba+x.fr)};
	}
}tr[MAXN*4];
void build(int i,int l,int r)
{
	if(l==r)
	{
		tr[i]=node{l,r,1,1,1,1,1,0};
		return ;
	}
	int mid=(l+r)>>1;
	build(i<<1,l,mid);
	build(i<<1|1,mid+1,r);
	tr[i]=tr[i<<1]+tr[i<<1|1];
}
void modify(node &x,int op)
{
	int len=x.r-x.l+1;
	if(op==0) x=node{x.l,x.r,0,0,0,0,0,-1};
	else x=node{x.l,x.r,len,len,len,x.l,len,1};
}
void down(int i)
{
	if(!tr[i].lazy) return ;
	if(tr[i].lazy==-1)
		modify(tr[i<<1],0),modify(tr[i<<1|1],0);
	else
		modify(tr[i<<1],1),modify(tr[i<<1|1],1);
	tr[i].lazy=0;
}
void updata(int i,int l,int r,int x)
{
	if(l<=tr[i].l && tr[i].r<=r)
	{
		if(x==0) modify(tr[i],0);
		else modify(tr[i],1);
		return ;
	}
	if(tr[i].l>r || l>tr[i].r) return ;
	down(i);
	updata(i<<1,l,r,x);
	updata(i<<1|1,l,r,x);
	tr[i]=tr[i<<1]+tr[i<<1|1];
}
int query(int i,int x)
{
	int t;
	if(tr[i].Max<x) return 0;
	if(tr[i].fr>=x) return tr[i].l;
	if(t=query(i<<1,x)) return t;
	if(tr[i].mi>=x) return tr[i].loc;
	if(t=query(i<<1|1,x)) return t;
	if(tr[i].ba>=x) return tr[i].r-tr[i].ba+1;
	return 0;
}
int main()
{
	n=read();m=read();
	build(1,1,n);
	while(m--)
	{
		int op=read();
		if(op==1)
		{
			int x=read(),t=0;
			printf("%d\n",t=query(1,x));
			if(t)
				updata(1,t,t+x-1,0);
		}
		else
		{
			int x=read(),d=read();
			updata(1,x,x+d-1,1);
		}
	}
	return 0;
}

序列

一、题目

题目:
题目描述
给定n个正整数的序列a1,a2,a3…an,对该序列可执行下面的操作: 选择一个大于k的正整数ai,将ai的值减去1;选择ai-1或ai+1中的一个值加上1。 共给出m个正整数k,对于每次给定的正整数k,经过以上操作一定次数后,求出最长的一个连续子序列,使得这个子序列的每个数都不小于给定的k

输入格式
第一行两个正整数n和m。 第二行n个正整数,第i个正整数表示ai ; 第三行m个正整数,第i个正整数表示第i次给定的k。

输出格式
共一行,输出m个正整数,第i个数表示对于第i次给定的k,经过一定次数操作后,最长连续子序列的长度。
数据范围:
对于 30%的数据, n < 30 n<30 n<30
对于 50%的数据, n < = 2000 n<=2000 n<=2000
对于 100%的数据, n < = 1 , 000 , 000 , m < = 50 , k < = 1 0 9 , a i < = 1 0 9 n<=1,000,000 ,m<= 50, k <= 10^9, ai <= 10^9 n<=1,000,000,m<=50,k<=109,ai<=109

二、解法

0x01 暴力
首先翻译一下题目,我们需要求一段区间 [ l , r ] [l,r] [l,r],使得 s u m [ r ] − s u m [ l ] − ( r − l + 1 ) ∗ x ≥ 0 sum[r]-sum[l]-(r-l+1)*x\geq 0 sum[r]sum[l](rl+1)x0,因为只要一个区间和大于等于给定的平均值,通过若干操作,就能使它满足条件。
那很显然有一个暴力的做法,求出前缀和后枚举就行了。
0x02 单调栈
我们发现题目要求的复杂度是 O ( n ∗ m ) O(n*m) O(nm) O ( n ∗ m ∗ l o g n ) O(n*m*logn) O(nmlogn)
但我们发现现在难以优化,我们尝试构造题目的单调性。
v a l = s u m [ i ] − x × i val=sum[i]-x\times i val=sum[i]x×i,我们维护一个 v a l val val单调下降的栈,对于每一个元素,将它 l o w e r _ b o u n d lower\_bound lower_bound,找到第一个不大于它的数,更新答案即可。
但发现还是有点卡,我们尝试优化成 O ( n ∗ m ) O(n*m) O(nm)
我们先维护一个单调栈,然后从后往前跑,在单调栈中找到第一个不大于它的数,只是这里我们对栈顶进行 p o p pop pop,直到不满足 s u m [ i ] − s u m [ s [ t o p ] ] > = 0 sum[i]-sum[s[top]]>=0 sum[i]sum[s[top]]>=0跳出,最后用 t o p + 1 top+1 top+1更新即可。
为什么能这样做了,原因是对于当前元素不优的话,那么对于前面的元素也一定不优(因为右端点是递减的),这时候满足了一个单调性,就可以这样做了。
0x03 代码

#include <cstdio>
#include <algorithm>
using namespace std;
#define LL long long
const int MAXN = 1000005;
int read()
{
	int x=0,flag=1;char c;
	while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
	while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return x*flag;
}
int n,m,x,s[MAXN],a[MAXN];
LL b[MAXN];
int max(int a,int b)
{
	if(a<b) return b;
	return a;
}
int main()
{
	n=read();m=read();
	for(int i=1;i<=n;i++)
		a[i]=read();
	while(m--)
	{
		int ans=0,top=0,x=read();
		s[top=1]=0;
		for(int i=1;i<=n;i++)
		{
			b[i]=b[i-1]+a[i]-x;
			if(b[s[top]]-b[i]>0) s[++top]=i;
		}
		for(int i=n;i>=1;i--)
		{
			while(top && b[i]-b[s[top]]>=0) top--;
			ans=max(ans,i-s[top+1]);
		}
		printf("%d ",ans);
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值