首页 > 技术文章 > P2134 百日旅行 (斜率优化,DP)

Kv-Stalin 2018-09-27 16:52 原文

题目链接


Solution

斜率优化\(DP\). 今天下午才打的第一道题 QwQ...
\(90\) 分很简单,一个简单的递推.
\(f[i]\) 为最后一天旅游的花费, \(g[i]\) 为最后一天吃饭的花费.
转移很简单:
\(f_i=min(f_i,g_j+p*(i-j)^2);\)
\(g_i=min(g_i,f_j+q*(i-j));\)
时间复杂度 \(O(n^2)\),然后看优化.

对于 \(g[i]\),可以直接从\(1\)\(i-1\)记录一个 \(Min(f[j]-q*j)\),然后就可以\(O(1)\)转移.
然后再看 \(f[i]\),将 \(f[i]\) 的转移方程整理后得:

\[f[i]+2*p*ij-p*i^2=g[j]+p*j^2 \]

此时化为 \(y=kx+b\) 的形式.
则令 :
\(2*p*j\)\(x\);
\(i\)\(k\);
\(f[i]-p*i\)\(b\) ;
\(g[j]+p*j^2\)\(y\) ;
且我们发现 \(k\) 递增,满足斜率优化要求.此处 \(g[i],g[j]\) 都已经求解出来.
那么此时我们要使得 \(f[i]\) 尽量小,也就是使得 \(b\) 最小.
然后就是用单调队列维护下凸包找到第一个比 \(k\) 大的斜率即可.

Q:为什么找第一个比 \(k\) 大的斜率??
此时我们需要使得 \(b\) 最小,\(b=x*(y/x-k)\) ,所以要使得 \(y/x\) 尽量小且大于 \(k\) 即可.

时间复杂度 \(O(n)\).

Code

//第一次打不会,只能对着别人打 QwQ...
#include<bits/stdc++.h>
#define ll long long
#define y(i) (g[i]+p*i*i)
#define x(i) (2*p*i)
using namespace std;
const int N=200005;
int n,r,l,Q[N];
ll p,q,Min,f[N],g[N];
int main(){
    cin>>n>>p>>q;
    for(int i=1;i<=n;i++)
    {
        g[i]=Min+q*i;
        while(r>l&&i*(x(Q[l+1])-x(Q[l]))>=y(Q[l+1])-y(Q[l]))l++;
        f[i]=g[Q[l]]+p*(i-Q[l])*(i-Q[l]);
        Min=min(Min,f[i]-q*i);
        while(r>l&&(y(i)-y(Q[r]))*(x(Q[r])-x(Q[r-1]))<=(x(i)-x(Q[r]))*(y(Q[r])-y(Q[r-1])))r--;
        Q[++r]=i;
        //单调队列维护第一个比 k 大的斜率.
    }
    printf("%lld\n",min(f[n],g[n]));
    return 0;
}

推荐阅读