【[JSOI2009]火星藏宝图】

这里是\(sb\)\(O(nm)\)做法

上一篇题解里写的\(O(nm)\)做法并没有看懂,我真是好菜啊

这是一个用了斜率优化,但是复杂度仍然是\(O(nm)\)的做法

我们还是先写出简单的\(dp\)方程

\(dp[i]\)表示到达第\(i\)个点的时候的最大收益

于是就有

\[dp[i]=max(dp[j]+w[i]-(a[i].x-a[j].x)^2-(a[i].y-a[j].y)^2)\]

显然暴力转移是\(O(n^2)\)

我们发现\(m\)非常的小,于是我们可以考虑一下把\(n\)优化成\(m\)

我们先将整个矩阵破坏成一条链,之后给每一个点一个新编号,对于原来的点\((x,y)\),新编号就是\((x-1)*m+y\),我们按照新编号给点从小到大排序

于是我们按照这个样子来进行\(dp\)的话就可以方便的找到某个点右上的所有点了

我们可以开上\(m\)个队列,第\(i\)个队列\(q[i]\)存储的是\(i\)这一列上面所有的点

于是我们更新一个点的时候只需要去枚举它之前的所有列对应的队列就好了

之后我们就可以来斜率优化了

由于我们枚举的是列数,那么\((a[i].y-a[j].y)^2\)就固定了,为了方便化柿子,我们设\(c=(a[i].y-a[j].y)^2\)

于是就有

\[dp[i]=dp[j]+w[i]-c-(a[i].x-a[j].x)^2\]

\[dp[i]=dp[j]+w[i]-c-a[i].x^2-a[j].x^2+2*a[i].x*a[j].x\]

\[-2*a[i].x*a[j].x+dp[i]+c-w[i]=dp[j]-a[j].x^2\]

这个柿子显然可以列率优化,截距为\(dp[i]+c-w[i]\),斜率为\(-2*a[i].x\)由于斜率单减,我们要求最大化截距,所以维护一个上凸壳就好了

代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define re register
#define maxn 1005
#define max(a,b) ((a)>(b)?(a):(b))
inline int read()
{
    char c=getchar();
    int x=0,r=1;
    while(c<'0'||c>'9') 
    {
        if(c=='-') r=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9')
        x=(x<<3)+(x<<1)+c-48,c=getchar();
    return x;
}
struct Point
{
    int x,y,rk;
    int w;
}a[200005];
int dp[200005];
int q[maxn][maxn],head[maxn],tail[maxn];
inline int cmp(Point K,Point M)
{
    return K.rk<M.rk;
}
#define X(i) (a[i].x)
#define Y(i) (dp[i]-a[i].x*a[i].x)
inline double K(int i,int j)
{
    return double(Y(i)-Y(j))/double(X(i)-X(j));
}
int n,m;
int main()
{
    n=read(),m=read();
    for(re int i=1;i<=n;i++)
    {
        a[i].x=read(),a[i].y=read(),a[i].w=read();
        a[i].rk=(a[i].x-1)*m+a[i].y;
    }
    std::sort(a+1,a+n+1,cmp);
    memset(dp,-20,sizeof(dp));
    dp[1]=a[1].w;
    q[1][++tail[1]]=1;
    for(re int i=2;i<=n;i++)
    {
        int T=a[i].y;
        for(re int j=1;j<=T;j++)
        {
            int c=(T-j)*(T-j);
            while(head[j]<tail[j]&&-2*a[i].x<K(q[j][head[j]],q[j][head[j]+1])) head[j]++;
            dp[i]=max(dp[i],dp[q[j][head[j]]]+a[i].w-c-(a[q[j][head[j]]].x-a[i].x)*(a[q[j][head[j]]].x-a[i].x));
        }
        while(head[T]<tail[T]&&K(i,q[T][tail[T]-1])>K(q[T][tail[T]],q[T][tail[T]-1])) tail[T]--;
        q[T][++tail[T]]=i;
    }
    std::cout<<dp[n];
    return 0;
}

转载于:https://www.cnblogs.com/asuldb/p/10205784.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值