「Luogu P3078 [USACO13MAR]扑克牌型Poker Hands」

本题有\(O(N)\)的优秀做法,但是因为在考场上不一定能想到,就来分享一种\(O(N\log_2N)\)的做法.虽然有点慢,但是可以过.

前置芝士

  1. 线段树:提高组及以上必备内容,不会的同学可以学习一下.

具体做法

只要会线段树就珂以了,是不是很简单.
先考虑贪心,连续的一定是一次去掉,不可能分成多次去取.
在这里插入图片描述
于是答案就是每一行连续的段数之和.
如图,第一行为\(1\)段,第二行\(2\)段,第三行\(2\)段,第四行为\(1\)段,所以答案就是\(1+2+2+1=6\).然后再考虑怎么去算出每一行的段数,如果暴力去求肯定是会T的需要\(O(N^2)\)的时间复杂度,好像也没有什么其他的快速求法,于是再考虑分治,没一次将最下方的若干个完整的一行去掉后就将问题分为若干个子问题,暴力的方法就可以跑过更多的点了,但还是可能被一些特殊的数据卡掉.
在这里插入图片描述
如这样一个数据,每一次查找当前区间最低的高度需要\(O(N)\)的时间复杂度,最终的时间复杂度就会退化成\(O(N^2)\).于是可以用线段树优化.时间复杂度就是\(O(N\log_2N)\)(大概).

代码

#include<bits/stdc++.h>
#define rap(i,first,last) for(int i=first;i<=last;++i)
//线段树标准define
#define Lson (now<<1)
#define Rson (now<<1|1)
#define Middle ((left+right)>>1)
#define Left Lson,left,Middle
#define Right Rson,Middle+1,right
#define Now nowleft,nowright
using namespace std;
const int maxN=5e5+7;
const int INF=123123123;//一个极大值
int N;
int tree[maxN*4];
int arr[maxN];
void PushUp(int now)//查询区间最大值
{
	tree[now]=min(tree[Lson],tree[Rson]);
}
void Build(int now=1,int left=1,int right=N)//建树
{
	if(left==right)
	{
		tree[now]=arr[left];
		return;
	}
	Build(Left);
	Build(Right);
	PushUp(now);
}
//不需要修改
int Query(int nowleft,int nowright,int now=1,int left=1,int right=N)//查询区间最小值
{
	if(right<nowleft||nowright<left)return INF;
	if(nowleft<=left&&right<=nowright)
	{
		return tree[now];
	}
	return min(Query(Now,Left),Query(Now,Right));
}
int QueryPlace(int nowleft,int nowright,int m,int now=1,int left=1,int right=N)
//查询区间下一个最小值的位置,m为最小值
{
	if(right<nowleft||nowright<left)return INF;
	if(left==right)
	{
		if(tree[now]==m)
		return left;
		return INF;
	}
	int result=INF;
	if(tree[Lson]<=m)//如果左区间可能存在就先查找左区间
	{
		result=QueryPlace(Now,m,Left);
	}
	if(result!=INF)return result;//存在就返回
	return QueryPlace(Now,m,Right);//不存在查找右区间
}
long long get(int l,int r/*当前的区间*/,int delhigh/*当前减去的高度*/)
{
	if(l>r)return 0;
	if(l==r)return arr[l]-delhigh;//l=r时就为当前剩余高度
	int minhigh=Query(l,r);
	int now=l-1,place;
	if(arr[l]==minhigh)now++;//为了防止陷入死循环
	long long result=(minhigh-delhigh);//先将最下方可以取的部分直接取掉
	while(1)//通过线段树将每一段区间找出并继续加上答案
	{
		place=QueryPlace(now+1,r,minhigh);
		if(place==INF)break;//如果没有下一个位置就退出
		result+=get(now+1,place-1,minhigh);//有就加上答案
		now=place;
	}
	if(arr[r]==minhigh)//为了防止陷入死循环
	result+=get(now+1,r-1,minhigh);
	else
	result+=get(now+1,r,minhigh);
	return result;//返回答案
}
int main()
{
	scanf("%d",&N);//读入
	rap(i,1,N)
	scanf("%d",&arr[i]);
	Build();//建树
	printf("%lld\n",get(1,N,0));//输出
	return 0;
}
posted @ 2020-01-25 18:24  SxyLimit  阅读(135)  评论(0编辑  收藏  举报