DP

斜率优化

只凸不凹

Posted by JU on July 20, 2018

注意

在将分母乘到另一边去的时候,注意正负性,尽量保证分母为正。

Luogu4072

i段路花了前l天来走的花费除以m

j1<j2j2优于j1

下凸壳(注意jj1,jj2,jj3是从右到左排列的)

jj1,jj2,jj3中,jj2最菜

#include <bits/stdc++.h>
#define pr(p) printf("%lld\n",p)
const int oo=2139063143;
const int N=3100;
const int mod=1000000007;
using namespace std;
typedef long long LL;
inline void sc (LL &x)
{
    x=0; static int p; p=1; static char c; c=getchar();
    while (!isdigit(c)) { if (c=='-') p=-1; c=getchar(); }
    while ( isdigit(c)) { x=(x<<1)+(x<<3)+(c-48); c=getchar(); }
    x*=p;
}
LL a[N],sum[N];
LL f[N][N];
LL q[N],head,tail;
#define j1 q[head]
#define j2 q[head+1]
#define jj1 i
#define jj2 q[tail]
#define jj3 q[tail-1]
LL S(LL a) { return a*a; }
int main()
{
    //freopen (".in","r",stdin);
    //freopen (".out","w",stdout);
    LL n,m; sc(n),sc(m);
    for (LL i=1; i<=n; i++)
        sc(a[i]),sum[i]=sum[i-1]+a[i],f[i][1]=S(sum[i]);

    for (LL l=2; l<=m; l++)
    {
        head=1,tail=0;
        for (LL i=1; i<=n; i++)
        {
            while (head< tail&&f[j2][l-1]-f[j1][l-1]+S(sum[j2])-S(sum[j1])<=2*sum[i]*(sum[j2]-sum[j1])) ++head;
            f[i][l]=f[j1][l-1]+S(sum[i]-sum[j1]);
            while (head< tail&&(f[jj2][l-1]-f[jj1][l-1]+S(sum[jj2])-S(sum[jj1]))*(sum[jj3]-sum[jj2])<=
                               (f[jj3][l-1]-f[jj2][l-1]+S(sum[jj3])-S(sum[jj2]))*(sum[jj2]-sum[jj1])) --tail;
            q[++tail]=i;
        }
        memset (q,0,sizeof (q));
    }
    pr(m*f[n][m]-S(sum[n]));
    return 0;
}

Luogu2900

#include <bits/stdc++.h>
#define sc(p) scanf("%lld",&p)
#define sc2(p1,p2) scanf("%lld%lld",&p1,&p2)
#define sc3(p1,p2,p3) scanf("%d%d%d",&p1,&p2,&p3)
#define pr(p) printf("%d\n",p)
const int oo=2147483640;
const int N=1010000;
const int mod=1000000007;
using namespace std;
struct MUD { long long h,w; }sb[N];
bool ly (MUD A,MUD B) { return A.h>B.h; }
long long x[N],y[N],f[N];
long long q[N],head,tail;
#define j1 q[head]
#define j2 q[head+1]
#define jj1 i
#define jj2 q[tail-1]
#define jj3 q[tail-2]
int main()
{
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    long long n; sc(n);
    for (int i=1; i<=n; i++) sc2(sb[i].h,sb[i].w);
    sort (sb+1,sb+n+1,ly);
    int ma=0,tot=0;
    for (int i=1; i<=n; i++)
    {
        if (sb[i].w<=ma) continue;
        x[++tot]=sb[i].w; y[tot]=sb[i].h;
        ma=x[tot];
    }
    n=tot;
    q[0]=0,head=0,tail=1;
    for (int i=1; i<=n; i++)
    {

        while (head+1<tail&& f[j2]-f[j1] <= x[i]*(y[j1+1]-y[j2+1]))
        head++;
        f[i]=f[j1]+x[i]*y[j1+1];
        while (head+1<tail&& (f[jj2]-f[jj1])*(y[jj2+1]-y[jj3+1]) <= 
                             (f[jj3]-f[jj2])*(y[jj1+1]-y[jj2+1])) tail--;
        q[tail++]=i;
        //cout<<f[i]<<" "; 
    }
    cout<<f[n];
    return 0;
}

斜率优化

概念

用于优化形如DP转移方程。

BZOJ3437

首先设f[i]表示在第i个位置建站的最小花费。
列出状态转移方程:
拆开Sigma得到
再拆成两个部分和
最后的形式为

重点1:设j1<j2,且j2优于j1。

易得
fs[i]=f[i]+sum2[i]
代入并变形得
再变形得
将其视为用两点表示直线的斜率,斜率优化由此得名。

双端队列

每次枚举到一个i,就将其与队列首用上述规则比较,弹出部分不优的候选,计算i的答案。

重点2

j1j2j3为连续三点,k1k2为相邻两点连线斜率。当k1>k2,分类讨论易得j2无用,将其从队尾弹出。
弹完后,队尾插入i

程序

#include <bits/stdc++.h>
#define sc(p) scanf("%lld",&p)
#define pr(p) printf("%d\n",p)
const int N=1010000;
using namespace std;
long long a[N],b[N];
long long sum1[N]={0},sum2[N]={0},f[N]={0},fs[N];
int q[N],head,tail;
#define j1 q[head]
#define j2 q[head+1]
#define jj1 i
#define jj2 q[tail-1]
#define jj3 q[tail-2] 
int main()
{
    int n; sc(n);
    for (int i=1; i<=n; i++) sc(a[i]);
    for (int i=1; i<=n; i++) sc(b[i]),sum1[i]=sum1[i-1]+b[i],sum2[i]=sum2[i-1]+i*b[i];
    q[0]=0,head=0,tail=1;
    for (int i=1; i<=n; i++)
    {
        while (head+1<tail&&fs[j2]-fs[j1]<=i*(sum1[j2]-sum1[j1]))    head++;

        f[i]=f[j1]+sum2[j1]-i*sum1[j1]+i*sum1[i]-sum2[i]+a[i];
        fs[i]=f[i]+sum2[i];

        while (head+1<tail&&(fs[jj2]-fs[jj1])*(sum1[jj3]-sum1[jj2]) <=
                            (fs[jj3]-fs[jj2])*(sum1[jj2]-sum1[jj1])) tail--;
        q[tail++]=i;
    }
    cout<<f[n];
    return 0;
}

滋磁

%%%罗梓璋 来自罗指导ppt

CC 原创文章采用CC BY-NC-SA 4.0协议进行许可,转载请注明:“转载自:斜率优化