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;
}