「一本通」斜率優化dp學習筆記
總結:
如果dp方程寫出來之後大概是長這樣的,就可以考慮斜率優化(關於斜率: )。先設<,分析一下是否有<(證明決策單調性),然後大力推一發柿子(通常是把關於的項移到座標,關於i的項移到右邊),找到座標和座標所對應的值,用單調佇列維護有用的決策,就可以亂搞ac了
loj#10184. 「一本通 5.6 例 1」任務安排 1
這題應該是先教你搞出dp公式 注意題意,執行任務的時間是同一批任務的所用時間總和+啟動時間,費用算的是每一個任務所屬批次的結束時間*費用係數 設為時間的字首和,為費用的字首和,$為執行1~i 的最少花費,列舉當前批次的任務為 j ~ i,顯然有當前當前批次的費用,機器的啟動時間S會對後面的所有任務的完成時間都產生一個大小為S的後延,補充到當前的費用中,此時,這題的資料範圍,可以放心 dp
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int list[5100];
long long f[5100],t[5100],c[5100];
int main()
{
int n,S;
scanf("%d%d",&n,&S);
for (int i=1;i<=n;i++)
{
long long tt,cc;
scanf("%lld%lld",&tt,&cc);
t[i]=t[i-1]+tt;
c[i]=c[i-1]+cc;
}
memset(f,63,sizeof(f));
f[0]=0;
for (int i=1;i<=n;i++)
{
for (int j=0;j<i;j++)
{
f[i]=min(f[j]+(t[i]+S)*(c[i]-c[j])+(c[n]-c[i])*S,f[i]);
}
}
printf("%lld\n",f[n]);
return 0;
}
loj#10185. 「一本通 5.6 例 2」任務安排 2
(事實證明用t1的做法是能過的)
有,試證明的決策比優秀
不想證,還是去看進階指南吧
拆括號,抵消,移項,得
右邊只保留t[i]的項,
得到公式啦!
根據“及時排出無用決策”,本題應是要維護一個下凸殼
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int list[11000];
long long f[11000],t[11000],c[11000];
int n,S;
int X(int i)
{
return c[i];
}
int Y(int i)
{
return f[i]-S*c[i];
}
double slop(int j1,int j2)
{
return double(Y(j2)-Y(j1))/double(X(j2)-X(j1));
}
int main()
{
scanf("%d%d",&n,&S);
for (int i=1;i<=n;i++)
{
long long tt,cc;
scanf("%lld%lld",&tt,&cc);
t[i]=t[i-1]+tt;
c[i]=c[i-1]+cc;
}
int head=1,tail=1;
for (int i=1;i<=n;i++)
{
while (head+1<=tail&&slop(list[head],list[head+1])<t[i]) head++;
f[i]=f[list[head]]+(t[i]+S)*(c[i]-c[list[head]])+(c[n]-c[i])*S;
while (head<=tail-1&&slop(list[tail],i)<slop(list[tail-1],list[tail])) tail--;
list[++tail]=i;
}
printf("%lld\n",f[n]);
return 0;
}
loj#10186. 「一本通 5.6 例 3」任務安排 3
https://loj.ac/problem/10186
時間可能為負數,因此時間的字首和t[i]也不具有單調性
而我們要維護斜率還是具有單調性的!!
結束刪隊頭的操作,最後出來的list[head]左邊的斜率小於t[i],右邊的斜率大於t[i]
所以通過 二分查詢到 左邊的斜率小於t[i],右邊的斜率大於t[i]的點
!!sd模版騙我錢財!! 數字太大還是要把除移項變成乘好一點
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
int list[310000];
LL f[310000],t[310000],c[310000];
int n,S;
LL X(int i)
{
return c[i];
}
LL Y(int i)
{
return f[i]-S*c[i];
}
double slop(int j1,int j2)
{
return double(Y(j2)-Y(j1))/double(X(j2)-X(j1));
}
int main()
{
scanf("%d%d",&n,&S);
for (int i=1;i<=n;i++)
{
int tt,cc;
scanf("%d%d",&tt,&cc);
t[i]=t[i-1]+tt;
c[i]=c[i-1]+cc;
}
int head=1,tail=1; list[1]=0;
for (int i=1;i<=n;i++)
{
//while (head+1<=tail&&slop(list[head],list[head+1])<t[i]) head++;
int l=head,r=tail;
while (l<r)
{
int mid=(l+r)/2;
if (f[list[mid]]-f[list[mid+1]]>=(S+t[i])*(c[list[mid]]-c[list[mid+1]])) l=mid+1; else r=mid;
}
f[i]=f[list[l]]-(S+t[i])*c[list[l]]+t[i]*c[i]+S*c[n];
while (head<=tail-1&&(f[list[tail]]-f[list[tail-1]])*(c[i]-c[list[tail]])>=(f[i]-f[list[tail]])*(c[list[tail]]-c[list[tail-1]])) tail--;
list[++tail]=i;
}
printf("%lld\n",f[n]);
return 0;
}
loj#10187. 「一本通 5.6 例 4」Cats Transport
https://loj.ac/problem/10187 為d的字首和,即1號山到i號山的距離 如果想要接到第i只貓,就得在時或之後出發,才能在貓玩夠之後到達 令,對a排序,根據貪心策略,一個飼養員接到的貓一定是a[]排序後連續的一段,那麼此時問題又回到了任務安排2了
設為a[ ]的字首和,f[i][j]為前i個飼養員,接到前j只貓的最小等待時間 有dp方程
設,因為時間,路程均為正整數,一定有k2優於k1 拆括號,抵消,移項,注意,得
上一題血的教訓告訴我寫程式碼還是把它弄成乘法吧
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define LL long long
LL d[110000],a[110000],s[110000],list[110000];
LL f[110][110000];//f[飼養員i][收回前j只貓]
bool cmp(LL x,LL y) {return x<y;}
LL Y(LL i,int k)
{
return f[i][k]+s[k];
}
LL X(LL k)
{
return k;
}
int main()
{
int n,m,p;
scanf("%d%d%d",&n,&m,&p);
for (int i=2;i<=n;i++) {scanf("%d",&d[i]);d[i]+=d[i-1];}
for (int i=1;i<=m;i++)
{
int h,t;
scanf("%d%d",&h,&t);
a[i]=t-d[h];
}
sort(a+1,a+1+m,cmp);
for (int i=1;i<=m;i++) s[i]=s[i-1]+a[i];
memset(f,63,sizeof(f));
for (int i=0;i<=p;i++) f[i][0]=0;
for (int i=1;i<=p;i++)
{
int head=1,tail=1; list[1]=0;
for (int j=1;j<=m;j++)
{
while (head+1<=tail&& (Y(i-1,list[head])-Y(i-1,list[head+1]))>=a[j]*(list[head]-list[head+1])) head++;
f[i][j]=min(f[i-1][list[head]]+a[j]*(j-list[head])-(s[j]-s[list[head]]),f[i-1][j]);
if (f[i-1][j]+s[j]>=455743088879883099) continue;
while (head<=tail-1&& (Y(i-1,list[tail-1]) - Y(i-1,list[tail])) * (list[tail]-j) > (Y(i-1,list[tail])-Y(i-1,j))*(list[tail-1]-list[tail])) tail--;
list[++tail]=j;
}
}
printf("%lld\n",f[p][m]);
return 0;
}
loj#10188. 「一本通 5.6 練習 1」玩具裝箱
https://loj.ac/problem/10188 sum[i]為玩具長度的字首和 顯然有 令c[i]=sum[i]+i,L=1+L dp方程: 設 (我喜歡用k) 把看作一個整體,完全平方公式來一發 繼續拆括號,移項,得