CF311B Cats Transport 斜率优化 dp
CF311B Cats Transport 斜率优化 dp
这个题的难度在于如何设计枚举顺序,不难发现,按照山丘来枚举或时间来枚举都不好,我们可以按照等待时间来枚举。
这样枚举有一个好处是:当你钦定一定要 恰好 选猫 \(i\) 时,所有在 \(i\) 前面的猫可以顺道带走,所有在 \(i\) 后面的猫都不能带。
状态的设计就非常自然了:设 \(f_{i,j}\) 表示前 \(i\) 个铲屎官拿前 \(j\) 个猫的最优解,则:
\[f_{i,j}=\min\limits_{i-1\le k\le j-1} \{f_{i-1,k}+\sum\limits_{a=k+1}^j (d_j-d_a) \}
\]
其中 \(d_a\) 为恰好取猫 \(a\) 所的对应出发时间。
后面那个和式可以写成:
\[\sum\limits_{a=k+1}^jd_a\sum\limits_{a=k+1}^jd_a
\]
\(k\times d_j\) 既有 \(k\) 也有 \(j\) ,所以可以用斜率优化。
单调性分析
不难发现,\(d_j\) 因为排序而单调递增。
事实上,对应每个 \(j\) 的最优决策 \(k\) 也是非严格单调递增的。所以我们可以用单调队列去维护下凸壳。
我们来证明一下上面这个事情。
-
证明:
用反证法。假设 \(f_{i,j}\) 的最优解为 \(k\) ,\(f_{i,j+1}\) 的最优解为 \(k'\) ,其中 \(k>k'\) ,所以我们有:
\[f_{i-1,k}+\sum\limits_{a=k+1}^j(d_{j}-d_a) <f_{i-1,k'}+\sum\limits_{a=k+1}^{j} (d_j-d_a)\\ \Rightarrow f_{i-1,k}+\sum\limits_{a=k+1}^{j+1}(d_j-d_a)<f_{i-1,k'}+\sum\limits_{a=k+1}^{j+1}(d_j-d_a)<f_{i-1,k'}+\sum\limits_{a=k'+1}^{j+1}(d_j-d_a) \]这与 \(f_{i,j+1}\) 的最优解为 \(k'\) 矛盾。可知上述结论成立。
代码:
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define int ll
#define ull unsigned long long
#define N 100010
#define M 110
using namespace std;
const int INF=0x3f3f3f3f;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
int n,m,p,D[N],sumD[N],f[M][N],sumd[N],d[N],h[N],t[N];
int q[N],l,r;
inline dd calc_y(int id,int k){
return f[id-1][k]+sumd[k];
}
inline dd calc_x(int k){
return k;
}
inline dd calc_k_1(int k){
return d[k];
}
inline dd calc_k_2(int id,int k1,int k2){
dd x1=calc_x(k1),x2=calc_x(k2);
dd y1=calc_y(id,k1),y2=calc_y(id,k2);
return (y1-y2)/(x1-x2);
}
inline void prework(){
for(int j=1;j<=m;j++) f[1][j]=j*d[j]-sumd[j];
}
signed main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
read(n);read(m);read(p);
for(int i=2;i<=n;i++) read(D[i]),sumD[i]=sumD[i-1]+D[i];
for(int i=1;i<=m;i++){
read(h[i]);read(t[i]);
d[i]=t[i]-sumD[h[i]];
}
sort(d+1,d+m+1);
// printf("d: ");
// for(int i=1;i<=m;i++) printf("%d ",d[i]);
// printf("\n");
for(int i=1;i<=m;i++) sumd[i]=sumd[i-1]+d[i];
prework();
// for(int j=1;j<=m;j++) printf("zhuangtai: i:%d j:%d f:%d\n",1,j,f[1][j]);
for(int i=2;i<=p;i++){
l=r=0;q[++r]=i-1;
for(int j=i;j<=m;j++){
while(l<r-1&&calc_k_2(i,q[l+1],q[l+2])<calc_k_1(j)) l++;
if(l<r){
int k=q[l+1];
// printf("juece: i:%d j:%d k:%d\n",i,j,k);
f[i][j]=f[i-1][k]+(j-k)*d[j]-sumd[j]+sumd[k];
}
while(l<r-1&&calc_k_2(i,q[r-1],q[r])>calc_k_2(i,q[r],j)) r--;
q[++r]=j;
// printf("zhuangtai: i:%d j:%d f:%d\n",i,j,f[i][j]);
}
}
printf("%lld",f[p][m]);
return 0;
}
d