石油大 2019年第二阶段我要变强个人训练赛第十八场 Problem N 扶桑号战列舰(线段树+区间更新+区间查询)

链接:http://icpc.upc.edu.cn/problem.php?cid=1803&pid=13

题意:给出一个n,接下来一行给出n个数。才开始所有数为0,每次操作可以选一个区间[l,r]使得区间内的数全部+1。问最少的操作次数得到这个序列,并且输出每步的区间。

思路:线段树维护区间内的最小数以及下标。枚举左端点,右端点肯定越大越优。每次先找出a[i]剩下多少,再迭代的从[i,n]区间中找可行的最靠右的最小值(假设下标为r),每次更新[i,r]的值,直到a[i]减为0。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e5+10;
int n,a[N],minval,minpos; 
struct Node
{
	int l,r,val,pos,lazy;
	//val:[l,r]中最小的值,pos是下标 
	//lazy:val的该变量 
}tree[N<<2];
//答案 
struct node
{
	int l,r,num;
	node(){}
	node(int l,int r,int num):l(l),r(r),num(num){}
}ans[N];
void pushup(int cur)
{
	tree[cur].val=min(tree[cur<<1].val,tree[cur<<1|1].val);
	if(tree[cur<<1].val<=tree[cur<<1|1].val)
		tree[cur].pos=tree[cur<<1].pos;
	else
		tree[cur].pos=tree[cur<<1|1].pos;
	return ;
}
//区间更新值,lazy标记 
void pushdown(int cur)
{
	if(tree[cur].lazy)
	{
		tree[cur<<1].lazy+=tree[cur].lazy;
		tree[cur<<1].val+=tree[cur].lazy;

		tree[cur<<1|1].lazy+=tree[cur].lazy;
		tree[cur<<1|1].val+=tree[cur].lazy;
		
		tree[cur].lazy=0;
	}
	return ;
}
void build(int l,int r,int cur)
{
	tree[cur].l=l;
	tree[cur].r=r;
	tree[cur].lazy=0;
	if(l==r)
	{
		tree[cur].val=a[l];
		tree[cur].pos=l;
		return ;
	}
	int m=(l+r)>>1;
	build(l,m,cur<<1);
	build(m+1,r,cur<<1|1);
	pushup(cur);
	return;
}
void update(int l,int r,int cur,int val)
{
	if(l<=tree[cur].l&&tree[cur].r<=r)
	{
		tree[cur].val+=val;
		tree[cur].lazy+=val;		
		return ;
	}
	pushdown(cur);
	if(l<=tree[cur<<1].r) update(l,r,cur<<1,val);
	if(r>=tree[cur<<1|1].l) update(l,r,cur<<1|1,val);
	pushup(cur);
	return ;
}
int query(int l,int r,int cur)
{
	int res=N;
	if(l<=tree[cur].l&&tree[cur].r<=r)
	{
		//更新该区间的最小值以及下标 
		if(tree[cur].val<=minval)
		{
			minval=tree[cur].val;
			minpos=tree[cur].pos;	
		}
		return tree[cur].val;
	}
	pushdown(cur);
	//先查左子树再查右子树,保证是最右的可行端点。 
	if(l<=tree[cur<<1].r) res=query(l,r,cur<<1);
	if(r>=tree[cur<<1|1].l) res=min(res,query(l,r,cur<<1|1));
	pushup(cur);
	return res;
}
int main(void)
{
	int cnt=0,sum=0,val,r;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	build(1,n,1);
	for(int l=1;l<=n;l++)
	{
		minval=N;
		val=query(l,l,1);	
		if(!val) continue;
		r=n;//尽可能往右,这样次数才更少,答案更优。 
		
		//迭代改变a[l]的值 
		while(val)
		{
			minval=N;
			query(l,r,1);
			
			val-=minval;
			
			if(minval)//minval如果为0,该答案不用记 
			{
				sum+=minval,ans[cnt++]=node(l,r,minval);
				update(l,r,1,-minval); 
			}		
			//minpos的数已变为0,再从[l,minspos-1]找最小值。 
			r=minpos-1;
		}
		
	}		

	printf("%d\n",sum);
	for(int i=0;i<cnt;i++)
	{
		for(int j=0;j<ans[i].num;j++)
			printf("%d %d\n",ans[i].l,ans[i].r);
	}
	
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值