http://acm.csu.edu.cn/csuoj/problemset/problem?pid=1963
題意:
有m個坑,每只兔子會在ti時刻回到坑中,現在有n個人,每個人都可以從任意時間(<0也可以)從第一個坑出發,速度為1,如果路過的坑中有兔子,就給它喂食物,每只兔子喂一次即可,計算所有兔子等待時間的最短之和。
思路:
對於第i只兔子,如果有個人在y時間正好經過喂了它,那么那個人出發的時間為y-x。現在現計算出每只兔子的y-x值,記為t【】,並排好序。sum【】表示前i只兔子的y-x值。
接下來就是動態轉移了,d【i】【j】表示第i個人正好經過第j只兔子時的最小等待時間。
那么狀態轉移方程就是
d[i][j]=min(d[i][j],d[i-1][k]+(j-k)*t[j]-(sum[j]-sum[k]))
解釋一下這個方程的意思:d【i-1】【k】表示上一個人正好經過第k只兔子時的最小等待時間,那么j~k之間的兔子肯定是要輪到第i個人來喂了,因為第i個人的出發時間更晚一些,所以j~k這些兔子肯定是要等待的,等待的時間就是,也就是上式的(j-k)*t[j]-(sum[j]-sum[k])。
理解了這個之后,接下來就是斜率優化dp的應用了,這樣的狀態轉移方程也容易讓人想到斜率優化dp。
設z<k<j,如果k的決策比z的決策更好,那么在上述的狀態轉移方程中滿足
d[i-1][k]+(j-k)*t[j]-(sum[j]-sum[k]) < d[i-1][z]+(j-z)*t[j]-(sum[j]-sum[z])
整理得到,那這就是明顯的斜率優化dp了。
所謂的斜率優化dp,就是每新插入一個點的時候,需要維護一條下凸的形狀,對於會形成上凸的點,我們可以直接刪去,因為它肯定不會是最優解。
那么如何維護呢?使用單調隊列。
每次依次先檢查隊首的兩個元素,如果滿足上式,那么說明隊首的第二個值更優,可以刪除第一個值,直到第一個值比第二個元素更優。
然后是隊尾的檢查,如果新插入的點使得隊尾的點成為了上凸點,那么就需要刪去這個上凸點,直到沒有上凸點產生。
1 #include<iostream> 2 #include<algorithm> 3 #include<cstring> 4 #include<cstdio> 5 #include<sstream> 6 #include<vector> 7 #include<stack> 8 #include<queue> 9 #include<cmath> 10 #include<map> 11 #include<set> 12 using namespace std; 13 typedef long long ll; 14 typedef pair<int,int> pll; 15 const int INF = 0x3f3f3f3f; 16 const int maxn = 1e5+ 5; 17 18 int n, m, p; 19 ll t[maxn]; 20 ll Q[maxn]; 21 ll sum[maxn]; 22 ll dis[maxn]; 23 ll d[105][maxn]; 24 25 ll dy(int i, int j, int k) 26 { 27 return (d[i-1][j]+sum[j])-(d[i-1][k]+sum[k]); 28 } 29 30 ll dx(int j, int k) 31 { 32 return j-k; 33 } 34 35 int main() 36 { 37 //freopen("in.txt","r",stdin); 38 while(~scanf("%d%d%d",&n, &m, &p)) 39 { 40 dis[1]=0; 41 for(int i=2;i<=n;i++) 42 { 43 scanf("%I64d",&dis[i]); 44 dis[i]+=dis[i-1]; 45 } 46 47 for(int i=1;i<=m;i++) 48 { 49 int x,y; 50 scanf("%d%d",&x,&y); 51 t[i]=y-dis[x]; 52 } 53 54 sort(t+1,t+m+1); 55 sum[0]=0; 56 for(int i=1;i<=m;i++) 57 sum[i]=sum[i-1]+t[i]; 58 59 for(int i=1;i<m;i++) 60 d[1][i]=t[i]*i-sum[i]; 61 62 63 for(int i=2;i<=p;i++) 64 { 65 int frt=0, rear=-1; 66 for(int j=1;j<=m;j++) 67 { 68 while(frt<rear && dy(i,Q[frt+1],Q[frt])<t[j]*dx(Q[frt+1],Q[frt])) frt++; 69 while(frt<rear && (dy(i,Q[rear],Q[rear-1])*dx(j,Q[rear]))>=(dy(i,j,Q[rear])*dx(Q[rear],Q[rear-1]))) 70 rear--; 71 Q[++rear]=j; 72 73 int tmp=Q[frt]; 74 d[i][j]=d[i-1][tmp]-(tmp-j)*t[j]-(sum[j]-sum[tmp]); 75 } 76 } 77 cout<<d[p][m]<<endl; 78 } 79 return 0; 80 }