斜率优化dp 队列版 专题学习小结

13 篇文章 0 订阅
6 篇文章 0 订阅

搞了一天的斜率优化,也算是有点入门吧。。。
斜率优化的题一般有个特点,在优化之前大都是区间dp的形式;
不过我只学了队列优化形式的,对于用栈来优化的还没有做过(蒟蒻逃
如果知道一个题是斜率优化dp,那么十有八九是做不错的,就我所做的裸的斜率优化dp大都遵循一个套路(那种套数据结构套cdq的另说)
首先必须要会推暴力的dp公式
然后再化简成d+kx=y的形式(没错我就是喜欢倒着写)
首先设要求的是i,从j这个点转移;
d是要求的dp值再加一堆乱七八糟的关于i恒定的值,总之就是知道d的值就能O(1)求出dp值就可以;
k是一个关于i的变量,应该要是一次的?(毕竟太高级的我也没做过
x是与j相关的变量;
y也是与j相关的变量;
就我所做的队列优化形式的dp来说,一般化简完以后x必须是单调的
而且k也要关于i单调;
这样就可以在凸包上寻找答案;
因为x是单调的,所以一定可以沿着一个方向来更新凸包,也不用担心会不会与被队列筛出去的点形成凸包;
因为k是单调的,所以就满足决策单调性……吧? 总之之前不能为i更新答案的点,也因为i+1这个点的斜率增加不会更新到i+1;
这样就可以很方便的用单调队列来解决了
其中有几点需要注意的是,
一般每个队列中存的是凸包上点对应的编号,而不是相应的坐标值,因为有可能坐标值是爆了数据范围的;

有的这类题每一个点的最优值是正好卡在longlong之下的,但是中间过程求凸包的时候却有可能爆longlong
解决方法之一是写个几位的高精度(划掉
也可以用double,中间过程求凸包的比较大小用double,虽然有误差,但也不会影响到大小的比较;

最后求一个点dp值的时候,比较方便的做法是套用最基础的公式,用队首的编号所对应的各项值直接算出;
毕竟一般很容易就能证明dp最优值是不会爆数据范围的

接下来放几个我今天水的题~都是公式推推推的没啥好说的

hnoi 玩具装箱toy

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define LL long long
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
using namespace std;
const int maxn=50005;
int que[maxn];
int n;
LL l;
LL dp[maxn];
LL s[maxn];
LL tp;
int head=0,tail=0;
double calc(int x,int y)
{
    return ((double)(dp[x]-dp[y])+(double)(s[x]+l)*(double)(s[x]+l)-(double)(s[y]+l)*(double)(s[y]+l))/(double)(s[x]-s[y]); 
}
void insert(int i)
{
    for(;tail-1>head;tail--)
    {
        int ta=que[tail-1];
        int tb=que[tail-2];
        if(calc(i,tb)>calc(ta,tb))break;
    }
    que[tail++]=i;
}
LL tpv;
LL ans=0;
double cal(int j,int i)
{
    return (double)dp[j]+(double)(s[j]+l)*(s[j]+l)-2.0*s[i]*(double)(s[j]+l)+(double)s[i]*s[i]; 
}
int main()
{
    scanf("%d%lld",&n,&l);
    l++;
    que[tail++]=0;
    s[0]=dp[0]=0;                       
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&tp);
        s[i]=s[i-1]+tp+1;
        for(head;head<tail-1;head++)
            if(cal(que[head],i)<cal(que[head+1],i))break;
        int j=que[head]; 
        dp[i]=dp[j]+(s[i]-s[j]-l)*(s[i]-s[j]-l);
        insert(i);
    }
    printf("%lld\n",dp[n]);
    return 0;
}

apio2010 特别行动队

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define LL long long
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
using namespace std;
const int maxn=1000005;
LL a,b,c;
LL dp[maxn];
int n;
int que[maxn];
LL sum[maxn];
double X(int i){
    return (double)((double)sum[i]*2*a-(double)b);
}
double Y(int i){
    return (double)((double)dp[i]+(double)c+(double)(a*sum[i]-b)*sum[i]);
}
double getval(int i,int j)
{
    return (double)a*sum[i]*sum[i]-(double)X(j)*sum[i]+(double)Y(j);
}
LL tp;
int tail=0,head=0;
int main()
{
    scanf("%d",&n);
    scanf("%lld%lld%lld",&a,&b,&c);
    sum[0]=dp[0]=0;
    que[tail++]=0;
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&tp);
        sum[i]=sum[i-1]+tp;
        for(;head<tail-1;head++)
            if(getval(i,que[head])>getval(i,que[head+1]))break;
        int j=que[head];
        dp[i]=dp[j]+(a*(sum[i]-sum[j])+b)*(sum[i]-sum[j])+c; 
        for(;tail-1>head;tail--){
            int j=que[tail-1];
            int k=que[tail-2];
            double X1=X(i)-X(j);
            double Y1=Y(i)-Y(j);
            double X2=X(j)-X(k);
            double Y2=Y(j)-Y(k);
            if(X1*Y2<Y1*X2)break; 
        }
        que[tail++]=i;
    }
    printf("%lld\n",dp[n]);
    return 0;
} 

sdoi 征途

#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<algorithm>
#define inf 1349880437
#define LL long long
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
using namespace std;
const int maxn=3005;
int n,m;
int que[maxn];
int tail,head;
int dp[maxn][maxn];
int sum[maxn];
int a[maxn];
int pow(int i,int j)
{
    return (sum[i]-sum[j])*(sum[i]-sum[j]);
}
double X(int i,int k)
{
    return (double)sum[i]*k;
}
double Y(int i,int k)
{
    return (double)((double)(dp[k-1][i]+(double)pow(i,0))/(double)(k-1)+(double)pow(i,0))*k;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        sum[i]=a[i]+sum[i-1];
    }
    for(int i=0;i<=m;i++)
    for(int j=0;j<=n;j++)dp[i][j]=inf;
    dp[0][0]=0;
    for(int i=1;i<=n;i++)dp[1][i]=0;
    for(int k=2;k<=m;k++)
    {
        head=tail=0;
        for(int i=k;i<=n;i++)
        {
            int j=i-1;
            for(;tail-1>head;tail--){
                double XX=X(j,k);
                double YY=Y(j,k);
                double X1=X(que[tail-1],k);
                double Y1=Y(que[tail-1],k);
                double X2=X(que[tail-2],k);
                double Y2=Y(que[tail-2],k);
                if((XX-X1)*(Y1-Y2)-(X1-X2)*(YY-Y1)<0)break;
            }
            que[tail++]=j;
            for(;head<tail-1;head++)
                if(Y(que[head],k)-sum[i]*X(que[head],k)*2<Y(que[head+1],k)-sum[i]*X(que[head+1],k)*2)break;
                j=que[head];
                dp[k][i]=((dp[k-1][j]+pow(j,0))/(k-1)+pow(i,j))*k-pow(i,0);
        }
    }
    printf("%d\n",dp[m][n]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值