【杂题总汇】HDU-6406 Taotao Picks Apples

【HDU 6406】Taotao Picks Apples

多校赛的时候多写了一行代码就WA了……找了正解对拍,在比赛结束后17分钟AC了😭


 

◇ 题目 +传送门+

<手写翻译>

有n个苹果按顺序排列,每个苹果都有一个高度 hi。由于陶陶有一种奇怪的癖好,他希望按照给出的顺序(输入顺序)摘苹果,且每一次摘的苹果的高度都严格大于上一次摘的苹果的高度。

神奇的是这棵树上的苹果会移动,能够改变其中一个苹果的高度,求改变后陶陶最多能够摘多少个苹果。

<输入输出>

第一行包含整数T不超过10,表示总共T组数据。

每组数据的第一行包含数n,m,表示树上共n个苹果,将会有m组小测试。接下来一行包含n个整数,表示每个苹果的高度,而陶陶也会按照这个顺序摘苹果。接下来的m行描述了一个小测试,包含p,q,表示第p个苹果移动到q的高度。

对于每一组小测试输出陶陶最多能够摘多少苹果。

<样例&解释>

  Input                   | Output            |  解释

1                         | 1                      |  (1)5 2 3 4 4,陶陶摘了第一个苹果后就不能摘了,后面没有比5高的苹果;

5 3                      | 5                      |  (2)1 2 3 4 5,陶陶可以摘完所有苹果;

1 2 3 4 4             | 3                      |  (3)1 3 3 4 4,陶陶可以摘第一、二、四个苹果;

1 5                      |                         |  

5 5                      |                         |  

2 3                      |                         |  


 

◇ 解析

先点出这道题所有的算法:单调栈,莫队,二分查找。(据说有一种DP的方法,但我还没有了解……🙃)

为了方便讲解,这里把变量声明一下:

①未更改高度时,陶陶可以摘的苹果的高度集合为 seq2[],可以摘的苹果的编号(输入顺序)的集合为 seq1[],可以摘的苹果的数量为 nseq,苹果的高度的集合为A[],前i个苹果中可以摘的苹果的个数 val[]【注意到这里seq1[i]和seq2[i]是一一对应的,即都描述的是第i个苹果】

②单调栈为 sta[],单调栈的栈顶编号为 beg

③询问存储在结构体 QUERY 里,包含更改的苹果的编号 pos,更改后苹果高度 num,询问的编号(按输入顺序) id,第i次询问的回答 ans[]

(1)莫队算法

改变输入顺序使计算速度增快或减少重复计算以简化代码难度。

这道题对莫队的运用还是很巧妙的。首先我们会发现,假设我们更改第i个苹果的高度,i及其之前陶陶能够摘的苹果数量为 res,分两种情况:

①i=seq1[j]:此时若 i 修改后的高度小于等于 seq2[j-1],则res会相对减少1(因为i不能够摘了);若修改后高度大于seq2[j-1],则 res 不变。

②i不属于seq1:seq1[k]是seq1中小于 i 的最大元素,即在i之前最接近i的陶陶能够摘的苹果,当 修改后的高度小于等于 seq2[k] ,则 res 不变(因为虽然不能取i,但seq1[1~k]仍然可以摘);当修改后的高度大于 seq2[k],则res增加1,因为除去原来可以摘的seq1[1~k],还可以摘i。

举一个简单的例子,方便理解😐:

所以只要预处理出seq1,seq2以及val,就可以比较方便地算出修改第i个苹果高度后i及其之前的苹果陶陶能够摘得的个数。

唯一难处理的是修改后,i后面可以摘的苹果的数量。这需要知道i后面的一个以大于i的高度的苹果开头的单调上升的序列。为方便求得这个序列,我们需要修改一下查询的顺序——从后向前。这样就可以利用单调栈不断地向栈里增减元素实现了。

(2)单调栈

从后往前依次将A[i]放入单调栈内,使单调栈内从顶到底为单调上升,若栈顶小于等于A[i],则删除栈顶,直到栈空或栈顶大于A[i],再放入A[i]。这一部分可以用二分查找(由于这里的栈是单调的),找到小于等于A[i]的第一个元素,修改其为A[i],并修改栈顶为当前位置。

(3)整体解法(reader们之前的看懂了吗?)

一边输入A[i],一边判断若seq2[]的末尾元素小于A[i],则更新seq1[]和seq2[],并增加nseq,同时更新val[i]为nseq(其实nseq就是陶陶摘得苹果的个数),如下:

for(int i=1,last=-1;i<=n;i++)
{
	scanf("%d",&A[i]);
	if(last<A[i])
		seq1[++nseq]=i,seq2[nseq]=A[i],last=A[i];
	val[i]=nseq;
}

将询问存储到QUERY结构体中,按照pos(修改的苹果位置)从后往前排序。

接下来从后向前(n~1)枚举 i ,若没有询问要修改位置i,则修改单调栈,用二分查找的方法找到单调栈中小于等于A[i]的最大元素的位置pos,更新其为A[i],更新栈顶beg为pos。

if(sta[beg]>A[I]) {sta[--beg]=A[I--];continue;} //栈顶比A[i]大,直接将A[i]放入栈顶
int pos=Find(sta,beg,n,A[I]); //二分查找找到pos
sta[pos]=A[I--];beg=pos; //更新栈顶

  若有询问修改位置i,则先处理询问,再将A[i]放入单调栈

为了方便修改,我们利用C++的引用,将 res 引用为当前问题的答案,初值赋为0。

在seq1中找到小于等于i的最大的元素的位置为left。若seq1[nseq](陶陶摘的最后一个苹果的位置)大于i,则直接将left赋值为nseq,否则二分查找找到left(lower_bound好像有点问题,不太会用)。若seq1[left]=i,则i原本就属于seq1,此时将left--,找到seq1的上一个元素。说明i之前(不包含i)陶陶能够摘left个苹果,将res+=left。当修改值为seq2[left-1],则说明i也能够摘,此时res++,否则将修改值改为seq2[left-1]。在单调栈中用二分查找找到大于修改值的元素的个数,如下:

if(qry[Q].num<sta[beg]) res+=n-beg+1;
//若栈顶大于修改值,则整个栈都大于修改值(单调上升),所以答案增加栈的长度(栈范围是beg~n)
else
	if(sta[n]>qry[Q].num) //栈底大于修改值,确保有解
	{
		int right=Find(sta,beg,n,qry[Q].num); //找到小于等于修改值的元素的位置
		res+=n-right; //剩余的都是大于修改值的
	}

最后输出答案就可以了……

感觉讲不太清楚😕真是抱歉……还是看代码吧……


 

◇ 源代码

/*Lucky_Glass*/
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=int(1e5);
int T,n,q; //数据组数,苹果个数,修改次数
int A[MAXN+5]; //原来苹果的高度
int seq1[MAXN+5],seq2[MAXN+5],nseq; //描述不修改时陶陶摘的苹果,nseq为个数,seq1,seq2方便描述陶陶摘的是第几个苹果、该苹果的高度
int val[MAXN+5]; //val[i]:在1~i个苹果中陶陶摘的苹果的个数
int sta[MAXN+5],beg; //单调栈,栈顶为beg,栈的范围在 beg~n
int ans[MAXN+5]; //ans[i] 第i个询问的答案
struct QUERY{ //查询
	int pos,num,id; //pos:修改苹果的位置,num:修改后高度,id:未排序(莫队)前是第几个询问
}qry[MAXN+5];
bool cmp(QUERY A,QUERY B){return A.pos>B.pos;} //按照修改苹果位置从后到前排序
int Find(int *GG,int l,int r,int x){ //二分查找,查找数组GG(QwQ)中从l~r里小于等于x的最大元素(返回其在GG中的位置)
	if(GG[r]<=x) return r; //r是访问不到的,先特殊处理
	while(l+1<r)
	{
		int mid=(l+r)>>1;
		if(GG[mid]<=x) l=mid;
		else r=mid;
	}
	return l;
}
int main(){
	//freopen("in.txt","r",stdin);
	scanf("%d",&T);
	while(T--)
	{
		nseq=0;beg=n; //初始化……
		memset(qry,0,sizeof qry);
		memset(seq1,0,sizeof seq1);
		memset(seq2,0,sizeof seq2);
		memset(sta,0,sizeof sta);
		memset(val,0,sizeof val);
		memset(ans,0,sizeof ans);
		memset(A,0,sizeof A);
		scanf("%d%d",&n,&q);
		for(int i=1,last=-1;i<=n;i++)
		{
			scanf("%d",&A[i]);
			if(last<A[i]) //当前苹果高于上一个摘的苹果,可摘
				seq1[++nseq]=i,seq2[nseq]=A[i],last=A[i]; //更新陶陶摘得的苹果
			val[i]=nseq; //存储1~i的答案
		}
		for(int i=1;i<=q;i++)
			scanf("%d%d",&qry[i].pos,&qry[i].num),qry[i].id=i; //读入询问
		seq2[0]==int(-1e9);seq1[0]=0; //小技巧,使得第一个苹果之前的高度永远小于它
		seq2[nseq+1]=seq1[nseq+1]=int(1e9); //使得最后一个苹果之后的高度永远最大(两个都是虚拟苹果)
		sort(qry+1,qry+1+q,cmp); //莫队
		for(int I=n,Q=1;I>=1;) //从后至前枚举苹果
		{
			if(qry[Q].pos==I) //对第i个苹果有修改
			{
				int &res=ans[qry[Q].id]; //引用,简化代码
				res=0; //清空
				int left; //1~i的苹果中陶陶最后摘的是第几个苹果
				if(seq1[nseq]<qry[Q].pos) //若修改的苹果在陶陶最后摘的苹果的后面
					left=nseq; //直接赋值为陶陶摘的最后一个苹果
				else
					left=Find(seq1,0,nseq,I); //二分查找
				if(seq1[left]==I) left--; //若找到的是当前苹果,则找到上一个
				res=left; //更新答案
				if(seq2[left]<qry[Q].num) res++; //第i个苹果修改后可以摘
				else qry[Q].num=seq2[left]; //不能摘,将修改值改为上一个摘的苹果的高度(实质是存储了1~i的苹果中陶陶最后摘的苹果的高度)
				if(qry[Q].num<sta[beg]) res+=n-beg+1;
				//若栈顶大于修改值,则整个栈都大于修改值(单调上升),所以答案增加栈的长度(栈范围是beg~n)
				else
					if(sta[n]>qry[Q].num) //栈底大于修改值,确保有解
					{
						int right=Find(sta,beg,n,qry[Q].num); //找到小于等于修改值的元素的位置
						res+=n-right; //剩余的都是大于修改值的
					}
				Q++;
			}
			else
			{
				if(sta[beg]>A[I]) {sta[--beg]=A[I--];continue;} //栈顶比A[i]大,直接将A[i]放入栈顶
				int pos=Find(sta,beg,n,A[I]); //二分查找找到pos
				sta[pos]=A[I--];beg=pos; //更新栈顶
			}
		}
		for(int i=1;i<=q;i++)
			printf("%d\n",ans[i]);
	}
	return 0;
}

  (感觉讲的好乱啊……有什么没看懂的还是直接问邮箱吧🙂)


 

The End

Thanks for reading!

- Lucky_Glass

(Tab:如果我有没讲清楚的地方可以直接在邮箱lucky_glass@foxmail.com email我,在周末我会尽量解答并完善博客~📃)
posted @ 2018-08-15 20:32  Lucky_Glass  阅读(731)  评论(0编辑  收藏  举报
TOP BOTTOM