POJ 3670 Eating Together 二分单调队列解法O(nlgn)和O(n)算法

本题就是一题LIS(最长递增子序列)的问题。本题要求求最长递增子序列和最长递减子序列。

dp的解法是O(n*n),这个应该大家都知道,不过本题应该超时了。

因为有O(nlgn)的解法。

但是由于本题的数据特殊性,故此本题可以利用这个特殊性加速到O(n)的解法,其中的底层思想是counting sort分段的思想。就是如果你不会counting sort的话,就很难想出这种优化的算法了。

O(nlgn)的单调队列解法,利用二分加速是有代表性的,无数据特殊的时候也可以使用,故此这里先给出这个算法代码。

看了代码就知道很简单的了,不过这里为了更加高效利用代码,就使用了函数指针,代码十分简洁了,初学者耐心点看,代码应该很好的:

#include <stdio.h>

const int MAX_N = 30000;
int arr1[MAX_N], arr2[MAX_N];
inline int max(int a, int b) { return a > b? a : b; }

inline bool larEqu(int a, int b) { return a <= b; }
inline bool smaEqu(int a, int b) { return a >= b; }

int biSearch(int low, int up, int val, bool (*func)(int , int))
{
	while (low <= up)
	{
		int mid = low + ((up-low)>>1);
		if (func(val, arr2[mid])) low = mid+1;
		else up = mid-1;
	}
	return low;
}

int getLIS(int n, bool (*func)(int, int))
{
	int j = 0;
	arr2[0] = arr1[0];
	for (int i = 1; i < n; i++)
	{
		if (func(arr1[i], arr2[j])) arr2[++j] = arr1[i];
		else arr2[biSearch(0, j, arr1[i], func)] = arr1[i];
	}
	return j+1;
}

int main()
{
	int n;
	while (scanf("%d", &n) != EOF)
	{
		for (int i = 0; i < n; i++)
		{
			scanf("%d", &arr1[i]);
		}
		printf("%d\n", n-max(getLIS(n, larEqu), getLIS(n, smaEqu)));
	}
	return 0;
}

然后是O(n)的时间效率的算法。

就是因为只有1,2,3,这三个数据,故此可以分开窗口,分别记录1, 2, 3 的数据段,在利用上面单调队列的思想的时候,就可以不使用二分法了,而是直接插入就可以了,故此省去了lgn的时间,时间效率就优化到O(n)了。

这个算法卡了我的地方就是下标的问题,老是无法准确记录窗口下标的,故此这里使用个特殊的记录下标的方法,看代码就好像是个O(n*n)的算法,因为循环中有循环,但是大家仔细看,其实这是个O(n)算法,为什么呢?因为循环中的循环总共只是搜索了一遍n个数,无需重复搜索。

#include <stdio.h>

const int MAX_N = 30000;
int arr1[MAX_N], arr2[MAX_N];
inline int max(int a, int b) { return a > b? a : b; }

int getLIS(int n)
{
	int j = 0, one = 0, two = 0;
	arr2[0] = arr1[0];
	for (int i = 1; i < n; i++)
	{
		if (arr1[i] >= arr2[j])
		{
			arr2[++j] = arr1[i];
		}
		else
		{
			if (arr1[i] == 1)
			{
				while (arr2[one] < 2 && one < j) one++;
				arr2[one] = arr1[i];
			}
			else
			{
				while (arr2[two] < 3 && two < j) two++;
				arr2[two] = arr1[i];
			}
		}
	}
	return j+1;
}

int getLDS(int n)
{
	int j = 0, two = 0, thr = 0;
	arr2[0] = arr1[0];
	for (int i = 1; i < n; i++)
	{
		if (arr1[i] <= arr2[j]) arr2[++j] = arr1[i];
		else
		{
			if (arr1[i] == 3)
			{
				while (arr2[thr] > 2 && thr < j) thr++;
				arr2[thr] = arr1[i];
			}
			else
			{
				while (arr2[two] > 1 && two < j) two++;
				arr2[two] = arr1[i];
			}
		}
	}
	return j+1;
}

int main()
{
	int n;
	while (scanf("%d", &n) != EOF)
	{
		for (int i = 0; i < n; i++)
		{
			scanf("%d", &arr1[i]);
		}
		printf("%d\n", n-max(getLIS(n), getLDS(n)));
	}
	return 0;
}

POJ 3670 Eating Together 二分单调队列解法O(nlgn)和O(n)算法,布布扣,bubuko.com

时间: 2024-10-11 10:02:44

POJ 3670 Eating Together 二分单调队列解法O(nlgn)和O(n)算法的相关文章

POJ 3670 Eating Together 二分解法O(nlgn)和O(n)算法

本题就是一题LIS(最长递增子序列)的问题.本题要求求最长递增子序列和最长递减子序列. dp的解法是O(n*n),这个应该大家都知道.只是本题应该超时了. 由于有O(nlgn)的解法. 可是因为本题的数据特殊性.故此本题能够利用这个特殊性加速到O(n)的解法.当中的底层思想是counting sort分段的思想.就是假设你不会counting sort的话,就非常难想出这样的优化的算法了. O(nlgn)的利用单调队列性质的解法,利用二分加速是有代表性的,无数据特殊的时候也能够使用.故此这里先给

[ACM] poj 2823 Sliding Window(单调队列)

Sliding Window Time Limit: 12000MS   Memory Limit: 65536K Total Submissions: 36212   Accepted: 10723 Case Time Limit: 5000MS Description An array of size n ≤ 106 is given to you. There is a sliding window of size k which is moving from the very left

[ACM] poj 2823 Sliding Window (单调队列)

高一时,学校组织去韶山游玩,我没去,这次趁着五一,总算去了我心心念念的韶山.其实我知道所有的景点都是差不多的,可是因为电视剧<恰同学少年>,让我对毛泽东有了进一层的了解,所以,我一直都想去看看. 有两个同学一男一女是我理想的旅友,可是女生不想去,而男士回家了.所以,我独自一人去了. 准备工作:一小包饼干,一小包山楂片,两个苹果,一瓶水,帽子(防晒),墨镜(装酷) 早晨5:30起床了,洗漱完毕,吃完早餐,赶到公交车站牌那里,才6点过几分.公交车6:31才到,等了近半个小时(公交车上明明说是6:0

HDU 3410 &amp;&amp; POJ 3776 Passing the Message 单调队列

题意: 给定n长的数组(下标从1-n)(n个人的身高,身高各不相同 问:对于第i个人,他能看到的左边最矮的人下标.(如果这个最矮的人被挡住了,则这个值为0) 还有右边最高的人下标,同理若被挡住了则这个值为0 输出n行,每个人左右2边的下标. 单调队列,对于 front - rear 的队列(注意出队都是在rear,入队也是在rear) 当加入元素x,若这队列是单调递增的,显然q.front() <= x , 反之若>x ,则把队首元素弹掉,这样就能保持单调性. 若弹掉了队首元素,在此题中就相当

POJ 2823 Sliding Window 【单调队列】

题目链接:http://poj.org/problem?id=2823 题目大意:给出一组数,一个固定大小的窗口在这个数组上滑动,要求出每次滑动该窗口内的最大值和最小值. 这就是典型的单调队列,单调队列的作用就在此.单调队列的队首为区间内的最值,但是整个队列不用保持单调. 用两个队列分别处理最大值和最小值,在此说明一下最大值: 往队列中添加值num时,从队尾开始扫,直到遇到一个小于num的d值,将num插入d的后一位.之后的元素全部无效化(不管后面的元素就行).查找最大值的时候,从队首开始找,如

POJ 3670 Eating Together

Description The cows are so very silly about their dinner partners. They have organized themselves into three groups (conveniently numbered 1, 2, and 3) that insist upon dining together. The trouble starts when they line up at the barn to enter the f

POJ 1742 Coins 多重背包单调队列优化

http://poj.org/problem?id=1742 题意: 很多硬币,有价值和数量,给出一个上限,问上限内有多少种钱数可以由这些硬币组成. 分析: 好像是楼教主男人八题之一.然后学多重背包单调队列优化时看了别人的程序..所以后来写了就1A了=.= 前一篇小小总结了一下多重背包单调队列优化(http://www.cnblogs.com/james47/p/3894772.html),这里就不写了. 1 #include<cstdio> 2 #include<cstring>

POJ 1742 Coins ( 单调队列解法 )

题目链接~~> 做题感悟:第一次做的时候用的二进制优化,但是没注意到是险过,so也没去看单调队列的解法. 解题思路: 如果你做过单调队列的题,或者看过相关的博客就好理解这题了,博客.再加上这题体积与价值相等那么就更好做了.只有 j %v[ i ] 余数相同的才可以同时处理(j 指的是某个体积的值),在计算某个数的时候,只要计算前面的相同的余数中(在个数限制内)是否有 true(有放满的) 就可以了. 代码: #include<iostream> #include<sstream&g

hdu_5884_Sort(二分+单调队列)

题目链接:hdu_5884_Sort 题意: 有n个数,每个数有个值,现在你可以选择每次K个数合并,合并的消耗为这K个数的权值和,问在合并为只有1个数的时候,总消耗不超过T的情况下,最小的K是多少 题解: 首先要选满足条件的最小K,肯定会想到二分. 然后是如何来写这个check函数的问题 我们要贪心做到使消耗最小,首先我们将所有的数排序 然后对于每次的check的mid都取最小的mid个数来合并,然后把新产生的数扔进优先队列,直到最后只剩一个数. 不过这样的做法是n*(logn)2 ,常数写的小