题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=1096
一开始想了想费用流,然后被数据范围pass掉了,感觉dp更可行一些。
只想到一个O(n2)的做法,看到式子比较复杂,就感觉像是斜率优化。
dp[i]表示前i个工厂所求的最小费用,则第i个工厂一定会建一个仓库。转移方程为$dp[i]=min(dp[j]+\sum_{k=j}^{i-1}p[k]*(x[i]-x[k]))+c[i]$。
将式子展开,用sump表示p数组的前缀和,sumpx表示p数组*x数组的前缀和。
方程就变成了$dp[i]=min(dp[j] + c[i] + (sumx[i - 1] - sumx[j])*x[i] - (sumpx[i - 1] - sumpx[j]))$
然后设k<j<i时,存在从j转移到i比从k转移到i更优。
则$dp[j] + c[i] + (sumx[i - 1] - sumx[j])*x[i] - (sumpx[i - 1] - sumpx[j])<dp[k] + c[i] + (sumx[i - 1] - sumx[k])*x[i] - (sumpx[i - 1] - sumpx[k])$
移项展开可以得到$dp[j] + sumpx[j] - (dp[k] + sumpd[k]) < (sump[j] - sump[k])*x[i]$
设$f[i]=dp[i]+sumpx[i]$,$T[i]=sump[i]$。
则上式变成$(f[j]-f[k])/(T[j]-T[k])<x[i]$。这就是经典的斜率方程
在每次求dp[i]时我们已经得出了dp[j]和dp[k],则可以得到二维坐标(T[j],f[j]),(T[k],f[k])。
会发现,如果还存在一个点y,y<k,且$(f[k]-f[y])/(T[k]-T[y])>=x[i]$,则k点可以直接被pass掉,在二维坐标中,就是维护一个下凸包点集。
这样就可以使得每次最优转移点可以O(1)得到,程序复杂度为O(n)。
1 #include<bits/stdc++.h> 2 #define lson l,mid,i<<1 3 #define rson mid+1,r,i<<1|1 4 using namespace std; 5 typedef long long ll; 6 const int maxn = 1e6 + 10; 7 ll c[maxn], x[maxn], p[maxn]; 8 ll sump[maxn]; 9 ll sumpx[maxn]; 10 ll dp[maxn]; 11 ll q[maxn]; 12 ll check1(int j, int k) { 13 return dp[j] + sumpx[j] - dp[k] - sumpx[k]; 14 } 15 ll check2(int j, int k) { 16 return sump[j] - sump[k]; 17 } 18 int main() { 19 int n; 20 scanf("%d", &n); 21 for (int i = 1; i <= n; i++) 22 scanf("%lld%lld%lld", &x[i], &p[i], &c[i]); 23 for (int i = 1; i <= n; i++) 24 sump[i] = sump[i - 1] + p[i], sumpx[i] = sumpx[i - 1] + p[i] * x[i]; 25 int l = 1, r = 1; 26 q[l] = 0; 27 for (int i = 1; i <= n; i++) { 28 while (l < r&&check1(q[l + 1], q[l]) <= x[i] * check2(q[l + 1], q[l])) 29 l++; 30 dp[i] = dp[q[l]] + c[i] + (sump[i - 1] - sump[q[l]])*x[i] - (sumpx[i - 1] - sumpx[q[l]]); 31 while (l < r&&check1(q[r], q[r - 1])*check2(i, q[r]) >= check1(i, q[r])*check2(q[r], q[r - 1])) 32 r--; 33 q[++r] = i; 34 } 35 printf("%lld\n", dp[n]); 36 return 0; 37 }
原文地址:https://www.cnblogs.com/sainsist/p/11130188.html