HDU 3507 Print Article 斜率DP入门 【斜率比较要避免除0,所以要化简式子,要考虑2点重合情况】

2 篇文章 0 订阅

总体思路如:http://www.cnblogs.com/ka200812/archive/2012/08/03/2621345.html


其中这篇文章有一点是错的。


要维护的是是下凸壳,不是上凸壳~


大致有几个重点:f[i] = min(f[j] + (s[i]-s[j])^2) + m


然后这是n^2的会T


于是我们把对于求f[i]而言,写出从j,k转移的方程。


比较2个方程,在什么条件下j比k转移到i会更优秀。结果得到一个式子。(我就不写啦~)


然后这个式子经过整理,整合,发现写为形如(yj-yk)/(xj-xk) < sum[i]的形式(这道题的sum[i]显然是单调不降的,那篇文章没说,但是这个条件很重要,不然程序就麻烦啦)


上面的那种式子,可以简单的看为,如果求出f[?],就可以写出形如(x[?], y[?])的状态,并且利用(x[?],y[?]) 来计算后面的f[p]的值(p>?)


======分割线=======


形如(yj-yk)/(xj-xk) < sum[i]的形式,可以写成g(j,k)  (j>k, 为了保证xj-xk>=0)  

然后如果g(i,j) < g(j,k)   (i>j>k)  的话,那么j就是废点,因为如果g(i,j) < sum[?],那么i比j优,不管sum[?]是多少,i都比j优。那么j就是废点了。

当g(i,j) > sum[?]的时候, 那么sum[?]<g(i,j) < g(j,k)  ,说明g(j,k) > sum[?],那么说明k比j要优。那么j就废了。


所以任何相邻的编号,他们连线的斜率,一定是单调递增的(所以去掉了很多无用点)。而解,一定是选择其中一个点。这样的点组成,可以显然的看出,是一个下凸壳。

也就是看起来像y=x^2 在第一象限的图案( 显然事实的图案是离散的。。。并且是一顿一段的,不可能光滑连续哒!)


【可能出现2个点因为巧合,所以重叠!】


=====分割线=====


维护凸壳,显然就用一个栈可以实现啦。


求解呢? 如果sum是无序的,那么就比较麻烦(平衡树之类的)


考虑sum=p的情况, 对于所有的凸壳上的点,从左下角第一个点开始往右找,斜率在提升,所以只要斜率小于p,那么就是我们需要的转移的点。

比如斜率依次为 1 3 4 5 6 7 9 10 11 15, P=8时,则会选择斜率为g(q[7],q[6]) = 7  (第7个点,和第6个点的斜率为7),然后q[7]显然更优,因为满足g(q[7],q[6])<sum.

同理,q[7]比q[8]优,因为g(q[8],q[7])>sum

又因为sum是单调递增的(不单调递增真的前面推导很多都要分类讨论啦!我也不想考虑了……就按照单调递增来算),

所以如果选过q[7],那么q[1...6]都不可能再次选择了。 这和sum是单调增有关系,证明略,稍微想一想就能想出来了……


然后要考虑一个情况,就是3个点斜率相同~   这都是程序里考虑的问题了。


【比较2个斜率大小,不要比斜率!要化简公式避免除法,除法会溢出(除0)!!!】



【PS:关于如果存在比较符号的>= >是否取等于号的问题】

如果存在斜率如图所示:


我们3,4会出队。

如果程序中去掉等号的话……(源程序是有等于号的)

bool slope(int i, int j, int k)	//得出j,k斜率
{
	//判断i,j的斜率,和j,k的斜率谁更大
	//如果i,j斜率<=j,k斜率,那么就需要退了
	return (y(i) - y(j)) * (x(j) - x(k)) <= (y(j) - y(k)) * (x(i) - x(j)); //这一行的等于号去掉的话
}

如果没有等于号,就会得到如下图所示的情况


看似没有问题?是不是只要在求解的过程中注意判断就行了呢?

因为,一开始的假设中,我们假设yi = f[i] - sum[i]^2, x[i] = sum[i] 或者x[i] = 2 * sum[i]

如果题目给的数字a[i]有0的话,那么sum[i]不变,但是f[i]显然是非递减的。也就是说,可能出现若干个点的x坐标相同,


(上述都是其他重点。。如果不是我用转移结果来判断优劣的话……)

实际上的真正导致错误的原因,是可能先从2转移,再从3转移,再从4转移,再从5转移。导致M被加了很多次……

比如这组数据~~~

4 1

1


在3个点的时候,出现这样的情况


1,2两个点,因为sum为0,的原因,重叠了。这个时候,3应该操死1,2,然后直接连0. 但是因为1,2的斜率不存在,所以出现了遗漏,导致3并没有操死1,2的任何一个点。

这样,就导致出现了问题。也就是说,如果出现【重复点】的情况下,会导致错误。为了避免这种情况,我们在程序的slope函数中,就加上了小于等于号。这样就去掉了重复点,和斜率相同的点之类的情况。 当然,既然没有重复点,实际上

		while (head + 1 != tail && zhuan(q[head], i) >= zhuan(q[head + 1], i)) //如果队列里元素超过1个,那么就比较
			++head;
这一段中的>=号,改为 >也是可以的了。


貌似没有疑问了……


#include <cstdio>
#include <iostream>
#include <cstdlib>
#include <cstring>
using namespace std;


typedef long long LL;
const int maxn = 500000 + 100;
int n, m;
LL a[maxn], s[maxn];
LL q[maxn], tail, head;
LL f[maxn];
const LL inf = 1LL<<50;


LL y(int k)
{
	return f[k] + 1LL * s[k] * s[k];
}

LL x(int k)
{
	return 2LL * s[k];
}

bool slope(int i, int j, int k)	//得出j,k斜率
{
	//判断i,j的斜率,和j,k的斜率谁更大
	//如果i,j斜率<=j,k斜率,那么就需要退了
	return (y(i) - y(j)) * (x(j) - x(k)) <= (y(j) - y(k)) * (x(i) - x(j));
}

LL zhuan(int j, int i)//j<i,j向i转移的值
{
	return f[j] + (s[i] - s[j]) * (s[i] - s[j]) + m;
}

void init()
{
	tail = head = 0; //tail == head则为空
	for (int i = 1; i <= n; ++ i)	scanf("%lld", &a[i]);
	memset(s, 0, sizeof(s));
	for (int i = 1; i <= n; ++ i)	s[i] = s[i - 1] + a[i];
	q[tail++] = 0;
}

void doit()
{
	for (int i = 1; i <= n; ++ i)
	{
		while (head + 1 != tail && zhuan(q[head], i) >= zhuan(q[head + 1], i)) //如果队列里元素超过1个,那么就比较
			++head;
		f[i] = zhuan(q[head], i);
		while (head + 1 != tail && slope(i, q[tail - 1], q[tail - 2]))	
			--tail;
		q[tail++] = i;
	}
	printf("%lld\n", f[n]);
}

int main()
{
	while (~scanf("%d%d", &n, &m))
	{
		init();
		doit();
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值