斜率优化 Snowdrop修长廊 scu dp练习B题

传送门:点击打开链接

题意:要覆盖完n个点,要覆盖掉[i,j]的代价是(X[i]-X[j])*(X[i]-X[j])+W,求覆盖完所有点的最小代价

思路:这是一道非常裸的斜率优化。

网上讲解斜率优化的博客还是比较多的,斜率优化通常就是化简后,最后得到了一个斜率的式子

推荐个学习的链接

点击打开链接


我稍微讲下对于单调队列,我一开始看着比较费劲的地方。。

(下面内容均建立在已经读完上面那个链接的基础之上。。

用g[i,j]表示点i,j的斜率。假如之前已经有点k,j。现在考虑把i点放进去

如果有g[j,i]<g[k,j],那么j一定不是最优的,现在我们来证明:

下面这张图是j需要被删除的情况。


如果g[j,i]<sum[i],那么根据之前我们列出的不等式的含义,可以知道i比j更优。

如果g[j,i]>=sum[i],那么就有g[k,j]>g[j,i]>=sum[i],根据定义,如果g[k,j]<sum[i],则j最优,反之,现在是>=sum[i],所以k点最优

所以我们能够发现,无论怎样,这种情况下的J都需要删掉。

综上所述,我们就知道,我们需要维护一个斜率越来越大的点的集合,如下图


我们接下来说一下,队列需要如何来维护。

下面的sum是针对上面那个地址来的,其实对应了本道题目的第i个位置

我们用单调队列来维护上面这个图。


删除队首没用的点:

取队首第一个点和第二个点,计算斜率,记为k

如果k<=sum,说明第二个点更优。所以我们就把队首那个点删掉,直到k>sum或者队列为空


由于随着i往后,sum会越来越大。我们可以发现,sum增大以后,我们刚刚删掉的点也不可能是最优的

所以就证明了我们删除队首的一些点的正确性。


新添加点:从队尾加入点,先看i与队列最后一个点的斜率记为a,队列最后一个点和最后第二个点的斜率记为b。

如果a<=b,那么就把最后一个点删掉,直到a>b或者队列中没有点。


为了减少我们思考复杂度,我们可以写好3个函数,geup(),getdown(),getdp(),之后的程序就很容易写了


PS:我们仔细再看下上面这道题为什么能用单调队列。

因为sum会越来越大,说白了就是你把斜率不等式写出来,当符号是<时候,右边是单调递增。当符号是>时候,右边是单调递减

只有这种情况,才可以使用单调队列来斜率优化。

如果sum不是单调的,那么就必须写凸包,然后再在凸包上三分答案。

#include <map>
#include <set>
#include <cmath>
#include <ctime>
#include <stack>
#include <queue>
#include <cstdio>
#include <cctype>
#include <bitset>
#include <string>
#include <vector>
#include <cstring>
#include <sstream>
#include <iostream>
#include <algorithm>
#include <functional>
#define fuck(x) cout<<"["<<x<<"]";
#define FIN freopen("input.txt","r",stdin);
#define FOUT freopen("output.txt","w+",stdout);
//#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;

const int INF = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const int MX = 2e5 + 5;

LL A[MX], dp[MX], w;
int Q[MX], c, r;
LL getup(int i, int j) {
    return dp[j - 1] + A[j] * A[j] - (dp[i - 1] + A[i] * A[i]);
}
LL getdown(int i, int j) {
    return A[j] - A[i];
}
LL getdp(int i, int j) {
    return dp[j - 1] + (A[i] - A[j]) * (A[i] - A[j]) + w;
}
int main() {
    int T; //FIN;
    scanf("%d", &T);
    while(T--) {
        int n;
        c = 1; r = 0;
        scanf("%d%lld", &n, &w);
        for(int i = 1; i <= n; i++) {
            scanf("%lld", &A[i]);
        }
        sort(A + 1, A + 1 + n);

        for(int i = 1; i <= n; i++) {
            while(r - c + 1 >= 2 && getup(Q[c], Q[c + 1]) <= 2 * A[i]*getdown(Q[c], Q[c + 1])) c++;
            dp[i] = min(getdp(i, Q[c]), dp[i - 1] + w);
            while(r - c + 1 >= 2 && getup(Q[r], i)*getdown(Q[r - 1], Q[r]) <= getdown(Q[r], i)*getup(Q[r - 1], Q[r])) r--;
            Q[++r] = i;
        }
        printf("%lld\n", dp[n]);
    }
    return 0;
}


这里还介绍一种类似整体二分的思想的分治的方法,可以用来替代单调队列

不过单调队列总的复杂度是O(n),分治的方法总的复杂度是O(nlogn)

虽然变慢了一个log,但是代码编写上简单非常多。

void dfs(int l, int r, int dl, int dr) {
    //[l,r]表示现在更新[l,r]区间dp[i]的最优值
    //用j -> f(i),表示j是更新f(i)最优值的最优点
    //那么[dl,dr]表示更新dp([l,r])的点,一定在[dl,dr]范围内
    int mid = (l + r) >> 1; 
    int dm = dl;
    int g = inf;
    for (int i = dl; i <= dr; i++) {
        if(g < dp[i] + f(i, mid)) {
            g = dp[i] + f(i, mid);//记录更新dp[mid]的最优
            dm = i;//记录更新dp[mid]的最优点
        }
    }
    dp[mid] = g; //更新dp[mid]的值
    //因为上文叙述的单调性,
    //更新[l,mid-1]的最优点,一定在[dl,dm]范围内
    if(l < mid) dfs(l, mid - 1, dl, dm);
    //更新[mid+1,r]的最优点,一定在[dm,dr]范围内
    if(mid < r) dfs(mid + 1, r, dm, dr);
    //此份代码dfs顺序有点问题,并不正确,但是并不影响理解
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值