DP——斜率优化专题(pku3709,hdu3669)

pku3709:这是我从我的百度空间搬过来的一篇文章,百度空间发出来的代码太难看了,打算专业一些的内容还是放在这个blog上吧。

这是我第一次 “挣了八景” 的做斜率优化的题,以前多数时候都是撇几眼~~还有那个四边形不等式,不过该干的事早晚是得干啊~~

实际上斜率优化一点都不难,关键是理解了斜率不等式以及单调队列队尾的更新。

此题很容易得到O(n^2)的DP方程:f[i]=Min{f[j]+sum[i]-sum[j]-a[j+1]*(i-j)}。

然后我说一下斜率优化:

假设决策j1<j2并且j2优于(或者不差于)j1,那么

f[j1]+sum[i]-sum[j1]+a[j1+1]*(i-j1) >= f[j2]+sum[i]-sum[j2]-a[j2+1]*(i-j2)    ---------------  不等式(1)

化简得:[(f[j1]-sum[j1]+a[j1+1]*j1) - (f[j2]-sum[j2]+a[j2+1])] >= i*(a[j1+1]-a[j2+1])。

因为有序序列嘛,a[j2+1]>=a[j1+1], 所以a[j1+1]-a[j2+1] <= 0。

所以也可以写成:[(f[j1]-sum[j1]+a[j1+1]*j1) - (f[j2]-sum[j2]+a[j2+1])] / (a[j1+1]-a[j2+1]) <= i。

对于a[j1+1]==a[j2+1]的情况,不能用除法了,只能用乘法那个表达式,

所以,如果对于决策j1,j2满足上述表达式,则j2 优于 j1。

但是如果不满足上述表达式呢,如果不满足那么j2就一定比j1差吗??

NO!! 因为如果j1,j2不变,不等式(1)左边是个常数,不会变;而a[j1+1]-a[j2+1] <= 0,所以随着i的增加不等式右边是会变小的(或者不变),如果变小的话,那么有可能在某一个i位置不等式会成立,也就是说在以后较大的某个i,j2会优于 j1。

OK,然后我们看看怎样用单调队列结合这两个性质来解这个问题。

令dy(j1, j2) = (f[j1]-sum[j1]+a[j1+1]*j1) - (f[j2]-sum[j2]+a[j2+1]), dx(j1,j2) = a[j1+1]-a[j2+1]。

首先刚开始队首元素为0——很明显~~

然 后假设队列首尾指针head < tail 并且dy(queue[head],queue[head+1]) >= i*dx(queue[head],queue[head+1]),那么队首元素直接丢掉就可以了。因为i是递增的,如果当前queue[head]没有 queue[head+1]好,那么今后也不会。

队尾的操作要稍微难理解一点,不是那么直观:因为对于队尾的2个原素x, y来说,如果对于当前i,y比x要烂,那么由前面的证明:对于比较大的i,y不一定就比x烂,有可能比x好呢。那么对这种情况看来不好处理,但是我们来看 看队尾3个元素的情况:x,y,z,如果dy(x,y)/dx(x,y)>=dy(y,z)/dx(y,z),那么可以直接把y给删了。因为 dy(x,y)/dx(x,y)和dy(y,z)/dx(y,z)是个常数,对于某个i,如果dy(x,y)/dx(x,y)<=i的话,那么 dy(y,z)/dx(y,z)一定也小于等于i,也就是说:如果y优于x,那么z一定优于y,这个时候留着y就没用了。。。。直接删了。。。

对 于边界情形的一点感想:实际上上述判断斜率大小的方式在几何角度来说只能针对x1,x2都不是0或者仅有一个是0的情况,但是对于x1,x2都是0的情况 需要对y进行判断以确定是正无穷还是负无穷(在解析几何方面说实际上是无斜率,但在这个题中却是有意义的。),有兴趣的可以分情况去讨论一下。 dy(x,y)和dy(y,z)有四种极限取值方式,分别是:(+oo,+oo), (+oo, -oo), (-oo, +oo), (-oo, -oo),其中第1、2、4种情况可以用代码中的乘法运算比较来判断,但是如果出现第3种情况(这时y优于x,且y优于z),程序会把y删掉,所以这么判 断是错误的。但是这个程序却可以AC这个题,为什么??因为(-oo, +oo)的情况不会出现 - -,只会出现(-oo, -oo)的情况;因为如果dx(x,y)==0,那么a[x+1]==a[y+1],那么-sum[x]+a[x+1]*x <= -sum[y]+a[y+1]*y,而f[x]<=f[y]恒成立,所以(f[x]-sum[x]+a[x+1]*x) - ( f[y]-sum[y]+a[y+1]*y)是一定小于等于 0的,也就是说如果a[x+1]==a[y+1],则dy(x,y)一定是小于等于0的,所以对于本题来说是可以这样维护的。(说的有点乱,有兴趣的同学 可以在纸上画画,感觉对于边界的证明想的有点麻犯了;如有更好证明方法,欢迎提出一起讨论)。

好了,主体过程就是这些,需要注意几点:因为有个限制k,所以决策点需要延迟加入,然后就是数据——记得用long long,最好读得时候也用上,我读的时候用的int,然后WA了,改成long long就过了,好像按说用int也行,可能是我没处理好。

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

const int N = 500010;
typedef long long llg;

int n, k, queue[N];
llg sum[N], f[N], a[N];

llg dy(int j1, int j2)
{
    return  (f[j1]-sum[j1]+a[j1+1]*j1) - (f[j2]-sum[j2]+a[j2+1]*j2);
}

llg dx(int j1, int j2)
{
    return  (a[j1+1] - a[j2+1]);
}

void dp()
{
    int i, j, head, tail, x, y, z;
    head = tail = 0;
    queue[0] = 0;
    for(i = 1; i <= n; i++)
    {
        while(head<tail && dy(queue[head], queue[head+1])>=i*dx(queue[head], queue[head+1]))
            head++;
        j = queue[head];
        f[i] = f[j] + sum[i] - sum[j] - a[j+1]*(i-j);
        if(i >= 2*k-1)    //实际上是i-k+1>=k
        {
            z = i-k+1;
            while(head < tail)
            {
                x = queue[tail-1];
                y = queue[tail];
                if(dy(x,y)*dx(y,z) >= dy(y,z)*dx(x,y))  tail--;
                else  break;
            }
            queue[++tail] = z;
        }
    }
}

int main()
{
    int t, i;
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d%d", &n, &k);
        sum[0] = 0;
        for(i = 1; i <= n; i++)
        {
            scanf("%I64d", a+i);
            sum[i] = sum[i-1] + a[i];
        }
        dp();
        printf("%I64d\n", f[n]);
    }
    return 0;
}

hdu3669:

如果是pku3709明白了的话,那么hdu3669就比较好懂了。这个题是2010年哈尔滨regional现场赛的原题。

解法:对于输入的数据,先按高度h从大到小排序,然后高度相同的按w从小到达排序,这样就可以把可以合并的洞(即w'<=w && h'<=h)合并掉。然后生成的新序列满足h递减,w递增。这时候dp方程f[i][j] = Min{f[k][j-1]+p[k+1].w*p[i].h}就可以顺利进行斜率优化了。注意:必须要合并,否则p[i].w不能保持递增顺序。这个题用斜率优化貌似常数有点大,rp低或者服务器忙的时候很容易挂掉,听说四边形不等式可以再加一个优化使时间复杂度大大降低。嗯,明天重新认真的学一下四边形不等式,斜率优化暂时先这样吧。

#include <iostream>
#include
<cstdio>
#include
<cstring>
#include
<algorithm>
usingnamespace std;

constint N =50010;

typedef
longlong llg;

const llg inf = llg(1000000)*1000005;

struct node
{
int w, h;
}p[N];

int n, k, queue[N];
llg f[N][
105];

bool cmp(const node &a, const node &b)
{
if(a.h == b.h) return a.w < b.w;
elsereturn a.h > b.h;
}

llg dy(
int k1, int k2, int j)
{
return f[k1][j-1] - f[k2][j-1];
}

llg dx(
int k1, int k2)
{
return (llg)(p[k2+1].h - p[k1+1].h);
}

int main()
{
int i, j, pos, head, tail, x, y, z;
llg ans;
while(scanf("%d%d", &n, &k) != EOF)
{
for(i =1; i <= n; i++) scanf("%d%d", &p[i].w, &p[i].h);
sort(p
+1, p+n+1, cmp);
j
=1;
for(i =2; i <= n; i++)
{
if(p[i].w <= p[j].w) continue;
else
{
++j;
p[j]
= p[i];
}
}
n
= j;
if(k > n) k = n;
for(i =0; i <= n; i++)
for(j =0; j <= k; j++)
f[i][j]
=-1;
f[
0][0] =0;
ans
= inf;
for(j =1; j <= k; j++)
{
head
= tail =0;
queue[
0] = j-1;
for(i =1; i <= n; i++)
{
while(head<tail && dy(queue[head], queue[head+1], j)>=p[i].w*dx(queue[head], queue[head+1]))
head
++;
pos
= queue[head];
f[i][j]
= f[pos][j-1] + p[i].w*p[pos+1].h;
z
= i;
if(f[z][j-1] !=-1)
{
while(head < tail)
{
x
= queue[tail-1];
y
= queue[tail];
if(dy(x,y,j)*dx(y,z) >= dy(y,z,j)*dx(x,y)) tail--;
elsebreak;
}
queue[
++tail] = z;
}
}
ans
= min(ans, f[n][j]);
}
printf(
"%I64d\n", ans);
}
return0;
}

posted on 2011-08-01 21:47  Moon_1st  阅读(2033)  评论(0编辑  收藏  举报

导航