P5017 [NOIP 2018 普及组] 摆渡车 题解
题传
思路
首先,朴素的 dp 转移时容易的。
设
显然有:
朴素优化
首先,考虑一定不会有一个长于
其次,我们考虑,
至此,复杂度优化到了
斜率优化
将 dp 转移的式子进行拆分、移项,可得到以下的式子:
与一次函数的解析式
也就是说,dp 转移方程是一个一次函数。
可以将前面的每一个状态看作是一个坐标为
那么,其实就是求与前面所有的状态表示的点组成的凸包相切的直线。
由于求的是最小值,于是只维护下凸包即可。
可以用单调栈维护凸包。
由于斜率单调递增,于是可以使用单调队列,每次把不符合要求的点直接剔除。
时间复杂度
Code
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
#define fi first
#define se second
using namespace std;
const int N=4e6+100+10;
const int inf=0x3f3f3f3f3f3f3f3f;
pair<int,int> q[N];
int n,m;
int t;
int cnt[N];
int sum[N];
int f[N];
int l,r;
double slope(pair<int,int> x,pair<int,int> y)
{
if(x.fi==y.fi) return (x.se>y.se?-inf:inf);
return (double)(y.se-x.se)/(double)(y.fi-x.fi);
}
int ans=inf;
signed main()
{
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
cin.tie(0);
cout.tie(0);
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=1;i<=n;i++)
{
int x;
cin>>x;
cnt[x]++;
sum[x]+=x;
t=max(t,x);
}
for(int i=1;i<=t+m;i++) sum[i]+=sum[i-1],cnt[i]+=cnt[i-1];
l=1;
for(int i=0;i<=m-1;i++) f[i]=i*cnt[i]-sum[i];
for(int i=m;i<t+m;i++)
{
while(l<r&&slope(q[r-1],q[r])>slope(q[r],{cnt[i-m],f[i-m]+sum[i-m]})) r--;
q[++r]={cnt[i-m],f[i-m]+sum[i-m]};
while(l<r&&slope(q[l],q[l+1])<i) l++;
f[i]=q[l].se-i*q[l].fi+i*cnt[i]-sum[i];
}
for(int i=t;i<t+m;i++) ans=min(ans,f[i]);
cout<<ans;
return 0;
}