【杂题总汇】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