原文链接:https://blog.csdn.net/qq_41730082/article/details/86305056
例题:https://blog.csdn.net/qq_36038511/article/details/82906874
董老师依旧yyds 听得透透的! 这题是打印文章
可以直接算斜率,也可以用下面的方式,乘起来,就不用double了
// Luogu P5905 【模板】Johnson 全源最短路 #include<algorithm> #include<cstring> #include<iostream> #include<queue> #define N 500010 #define INF 1000000000 typedef long long LL; using namespace std; int n,m,q[N]; LL s[N],f[N]; double slope(int i,int j){ return (double)(f[i]+s[i]*s[i]-f[j]-s[j]*s[j])/(s[i]==s[j]?1e-9:s[i]-s[j]); //要特判,斜率为无穷大的情况 } int main(){ while(~scanf("%d %d",&n,&m)){ for(int i=1;i<=n;i++) { scanf("%d",&s[i]); s[i]+=s[i-1]; } int head=1,tail=0; for(int i=1;i<=n;i++){ while(head<tail&&slope(i-1,q[tail])<=slope(q[tail],q[tail-1])) tail--; //head<tail 保证队列里面有两个,才可以求出斜率 q[++tail]=i-1; while(head<tail&&slope(q[head+1],q[head])<=2*s[i]) head++; int j=q[head]; f[i]=f[j]+(s[i]-s[j])*(s[i]-s[j])+m; } printf("%lld\n",f[n]); } return 0; }
1606:【 例 1】任务安排 1
道题如果是用普通的dp你会不会做呢?
普通的dp的话复杂度是O(N^3)的,用dp[i][j]表示前i个任务划分了j段
那么普通的转移表达式就是:dp[i][j]=min(dp[i][j],dp[k][j-1]+(time_sum[i]+s*j)*(cost_sum[i]-cost_sum[k])) j-1<=k<=i-1
其实在后面不知道怎么推的时候,也可以先暴力写出表达式,找找思路
一看就是个 dp了,设 f[i]为前i个任务的最小花费。
发现题目中的 s不好处理,这里用到一个很优秀的技巧——费用提前。
具体是这样的:因为我们不知道之前用了多少个s,所以这里难以计算费用,换句话说,我们不知道之前对现在的贡献,但是,我们知道现在对未来的贡献!
假如当前把 x~y 这一段任务一起完成,那么事实上,我们将x~n 这一段任务的完成时间都延后了s,那么直接加上 (c[n]-c[x-1])×s ,c是任务费用系数的前缀和)即可。
那么得出方程:f[i]=mini({f[j]+(s[i]+s)×(c[i]-c[j])+s×(c[n]-c[j])} t 是任务的时间花费的前缀和
//这道题如果是用普通的dp你会不会做呢? //普通的dp的话复杂度是O(N^3)的,用dp[i][j]表示前i个任务划分了j段 //那么普通的转移表达式就是: //dp[i][j]=min(dp[i][j],dp[k][j-1]+(time_sum[i]+s*j)*(cost_sum[i]-cost_sum[k])) j-1<=k<=i-1 /* 一看就是个 dp了,设 f[i]为前i个任务的最小花费。 发现题目中的 s不好处理,这里用到一个很优秀的技巧——费用提前。 具体是这样的:因为我们不知道之前用了多少个s,所以这里难以计算费用,换句话说,我们不知道之前对现在的贡献,但是,我们知道现在对未来的贡献! 假如当前把 x~y 这一段任务一起完成,那么事实上,我们将x~n 这一段任务的完成时间都延后了s,那么直接加上 (c[n]-c[x-1])×s,c是任务费用系数的前缀和)即可。 那么得出方程:f[i]=mini({f[j]+(s[i]+s)×(c[i]-c[j])+s×(c[n]-c[j])} t 是任务的时间花费的前缀和 */ //求出T和F的前缀和st和sf,设f[i]表示完成前i个任务的最小费用,f[i]=min(f[i],f[j]+st[i](sf[i]-sf[j])+s (st[n]-st[j])),因为s的会对后j+1~n个任务产生影响所以提前累加。 #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; }
1607:【 例 2】任务安排 2
进阶的做法
//虽然这个上一道题的做法也就是(N^2)也能过
j<k<i,若k比j优 (Ps:为了方便C表示Cost,T表示Time)则dp[k]+S*(C[n]-C[k])+T[i]*(C[i]-C[k])<=dp[j]+S*(C[n]-C[j])+T[i]*(C[i]-C[j])
把括号拆掉 dp[k]+S*C[n]-S*C[k]+T[i]*C[i]-T[i]*C[k]<=dp[j]+S*C[n]-S*C[j]+T[i]*C[i]-T[i]*C[j]
消去同类 dp[k]-S*C[k]-T[i]*C[k]<=dp[j]-S*C[j]-T[i]*C[j]
移项得 dp[k]-dp[j]<=(S+T[i])*(C[k]-C[j]) 条件1
则(条件1)成立时 k 比 j 优 ,否则 j 比 k 优
(dp[k]-dp[j]) / (C[k]-C[j]) 是斜率
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=10010; const int INF=0x3fffffff; typedef long long LL; int n,s; LL tt[maxn],cost[maxn]; LL dp[maxn]; int que[maxn]; /* 设j<k<i,若k比j优 (Ps:为了方便C表示Cost,T表示Time) 则dp[k]+S*(C[n]-C[k])+T[i]*(C[i]-C[k])<=dp[j]+S*(C[n]-C[j])+T[i]*(C[i]-C[j]) 把括号拆掉 dp[k]+S*C[n]-S*C[k]+T[i]*C[i]-T[i]*C[k]<=dp[j]+S*C[n]-S*C[j]+T[i]*C[i]-T[i]*C[j] 消去同类 dp[k]-S*C[k]-T[i]*C[k]<=dp[j]-S*C[j]-T[i]*C[j] 移项得 dp[k]-dp[j]<=(S+T[i])*(C[k]-C[j]) 条件1 则(条件1)成立时 k 比 j 优 ,否则 j 比 k 优 (dp[k]-dp[j]) / (C[k]-C[j]) 是斜率 */ bool judge(int j,int k,int i){ //j<k<i,判断k是不是更优 (dp[k]-dp[j]) / (C[k]-C[j]) 是斜率 int s1=dp[k]-dp[j]; int s2=(s+tt[i])*(cost[k]-cost[j]); return s1<=s2 ? 1:0; } bool judge1(int j,int k,int i){ //j<k<i, int s1=(dp[k]-dp[j])*(cost[i]-cost[k]); int s2=(dp[i]-dp[k])*(cost[k]-cost[j]);//交换看看,其实是除法 return s1>=s2 ? 1:0; } int main(){ scanf("%d %d",&n,&s); for(int i=1;i<=n;i++){ int t,cc; scanf("%d %d",&t,&cc); tt[i]=tt[i-1]+t; cost[i]=cost[i-1]+cc; } dp[0]=0; que[1]=0; int head=1,tail=1; for(int i=1;i<=n;i++){ while(head<tail&&judge(que[head],que[head+1],i)) head++; int j=que[head]; dp[i]=dp[j]+s*(cost[n]-cost[j])+tt[i]*(cost[i]-cost[j]); while(head<tail&&judge1(que[tail-1],que[tail],i)) tail--; que[++tail]=i; } printf("%lld\n",dp[n]); return 0; }
1608:【 例 3】任务安排 3
t数组不再具有单调性,所以需要二分。而我们要维护斜率还是具有单调性的!!结束删队头的操作,最后出来的list[head]左边的斜率小于t[i],右边的斜率大于t[i]
所以通过 二分查找到 左边的斜率小于t[i],右边的斜率大于t[i]的点
这次的Ti可以是负的,所以就没有单调性了,但是凸包还是有单调性的,所有二分当前直线与凸包的切点就可以了(题解原话)
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=310000; const int INF=0x3fffffff; typedef long long LL; //时间可能为负数,因此时间的前缀和t[i]也不具有单调性 //而我们要维护斜率还是具有单调性的!! //结束删队头的操作,最后出来的list[head]左边的斜率小于t[i],右边的斜率大于t[i] //所以通过 二分查找到 左边的斜率小于t[i],右边的斜率大于t[i]的点 //这次的Ti可以是负的,所以就没有单调性了,但是凸包还是有单调性的,所有二分当前直线与凸包的切点就可以了(题解原话) //为什么是二分,没想明白 int n,s; LL dp[maxn],t[maxn],c[maxn],list[maxn]; LL X(int i){ return c[i]; } LL Y(int i){ return dp[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++){ //这里不再弹队头,因为t不具有单调性了 int l=head,r=tail; //二分找 while(l<r){ int mid=(l+r)/2; if(dp[list[mid]]-dp[list[mid+1]]>=(s+t[i])*(c[list[mid]]-c[list[mid+1]])) l=mid+1; else r=mid; } dp[i]=dp[list[l]]-(s+t[i])*c[list[l]]+t[i]*c[i]+s*c[n]; //你仔细看,是另一种计算方法 while (head<=tail-1&&(dp[list[tail]]-dp[list[tail-1]])*(c[i]-c[list[tail]])>=(dp[i]-dp[list[tail]])*(c[list[tail]]-c[list[tail-1]])) tail--; list[++tail]=i; } printf("%lld\n",dp[n]); return 0; }
1609:【例 4】Cats Transport
这次的dp数组变为二维了,我觉得最重要的是要想到对数组a[]进行排序,然后就转化为每个人取连续的一段。
这个思路是我欠缺的,因为人数增多了,最后返回的答案也是dp[p][m]
ps.下面写错了,Y()应该是相加
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1e5+10; const int INF=0x3fffffff; typedef long long LL; LL d[maxn],a[maxn],s[maxn],list[maxn]; LL dp[110][maxn]; //dp[饲养员i][收回前j只猫] LL X(int i){ return i; } LL Y(int i,int k){ return dp[i][k]+s[k]; } bool cmp(LL x,LL y){ return x<y; } 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]; } int h,t; for(int i=1;i<=m;i++){ scanf("%d %d",&h,&t); a[i]=t-d[h]; } sort(a+1,a+1+m,cmp) ; //对a进行排序 对a排序,根据贪心策略,一个饲养员接到的猫一定是a[]排序后连续的一段,那么此时问题又回到了任务安排2了 for(int i=1;i<=m;i++){ s[i]=s[i-1]+a[i]; //s是a的前缀和 } memset(dp,63,sizeof(dp)); for(int i=0;i<=p;i++) dp[i][0]=0; //初始化 for(int i=1;i<=p;i++){ //进行p次 每次都去一段连续的 int head=1,tail=1; list[1]=0; for(int j=1;j<=m;j++){ while(head<tail&&(Y(i-1,list[head])-Y(i-1,list[head+1]))>=a[j]*(list[head]-list[head+1])) head++; dp[i][j]=min(dp[i-1][j],dp[i-1][list[head]]+a[j]*(j-list[head])-(s[j]-s[list[head]])); if(dp[i-1][j]+s[j]>=455743088879883099) continue; while(head<tail&&((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--; //逐一比较,直到队列的节点不剩下的时候(其实那会应该剩下一个我们最初放入的0号节点,保证了可能是从第一位开始向后加的情况的元素), //或者是K1>K2的时候,我们结束循环,并且继续下一个元素。 list[++tail]=j; } } printf("%lld\n",dp[p][m]); return 0; }
1610:玩具装箱
https://www.cnblogs.com/gaojunonly1/p/10409738.html
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=5e4+10; const int INF=0x3fffffff; typedef long long LL; //典型题 int n,l; LL c[maxn],dp[maxn],list[maxn]; LL sqr(LL x) { return x*x; } LL X(int i){ return c[i]; } LL Y(int i){ return dp[i]+sqr(c[i]); } int main(){ scanf("%d %d",&n,&l); for(int i=1;i<=n;i++){ int cc; scanf("%d",&cc); c[i]=c[i-1]+cc+1; //因为要每个玩具之间有点间距,所以要加1 } int head=1,tail=1; list[0]=0; l++; for(int i=1;i<=n;i++){ //这里就相当于k1--head k2--head+1(k1<k2) while(head<tail&&((Y(list[head])-Y(list[head+1]))>=2*(c[i]-l)*(X(list[head])-X(list[head+1])))) head++; dp[i]=dp[list[head]]+sqr(c[i]-c[list[head]]-l); //这里需要满足斜率递增的条件,不然取不到最优 //相当于 tail-1到tail的斜率如果大于了 i到tail的斜率 那么就需要去除掉最后的tail while(head<tail&&(( Y(list[tail-1]) - Y(list[tail])) * (X(list[tail])-X(i)) >= ( Y(list[tail]) - Y(i)) * (X(list[tail-1]) - X(list[tail]))))tail--; //(head<=tail-1 && ( Y(list[tail-1]) - Y(list[tail])) * (X(list[tail])-X(i)) >= ( Y(list[tail]) - Y(i)) * (X(list[tail-1]) - X(list[tail]))) tail--; list[++tail]=i; } printf("%lld",dp[n]); return 0; }
1611:仓库建设
一样的,可以先写暴力的式子,然后一步一步推出
另 f[i]=f[j]+Calc(j+1,i),Calc(j+1,i)表示 j+1 到 i 的货物全部运到 i 的花费 这里的重点就是计算calc(j,i)是O(1)的
对于这个暴力统计是这样的:
for(k=j+1;k<=i;k++)
{
int SS=0;
SS+=(Dis[i]-Dis[j])*P[j];
}
设一个 Val[i]=Val[i-1]+P[i]*Dis[i]; 和一个P_Qzh[i]=P_Qzh[i-1]+P[i];
SS就是Calc(j+1,i),容易发现SS=Dis[i]*(P_Qzh[i]-P_Qzh[j])-(Val[i]-Val[j]);
然后就有了n2的暴力
然后套路的用斜率优化,过程如下
j<k<i (若k比j优)
dp[j]+Cost[i]+Dis[i]*(P_Qzh[i]-P_Qzh[j])-(Val[i]-Val[j]) (1)
--->dp[j]+Cost[i]+Dis[i]*P_Qzh[i]-Dis[i]*P_Qzh[j]-Val[i]+Val[j]
dp[k]+Cost[i]+Dis[i]*(P_Qzh[i]-P_Qzh[k])-(Val[i]-Val[k]) (2)
--->dp[k]+Cost[i]+Dis[i]*P_Qzh[i]-Dis[i]*P_Qzh[k]-val[i]+Val[k]
若(1)>=(2)
---> dp[k]-Dis[i]*P_Qzh[k]+Val[k] <= dp[j]-Dis[i]*P_Qzh[j]+Val[j]
---> (dp[k]+Val[k])-(dp[j]+Val[j]) <= Dis[i]*(P_Qzh[k]-P_Qzh[j])
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1e6+10; const int INF=0x3fffffff; typedef long long LL; //当一下子写不出来状态表达式时 //可以先试试用暴力思考,然后一步步分解这个表达式 int n; LL dis[maxn],pp[maxn],a[maxn],dp[maxn]; //pp是p(货物个数)的前缀和,a是p[i]*dis[i]的前缀和 int list[maxn],c[maxn],p[maxn]; //p是个数、c是建造的金额 LL X(int i){ return pp[i]; } LL Y(int i){ return dp[i]+a[i]; } int main(){ scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d %d %d",&dis[i],&p[i],&c[i]); pp[i]=pp[i-1]+p[i]; a[i]=a[i-1]+dis[i]*p[i]; } int head=1,tail=1; list[0]=0; for(int i=1;i<=n;i++){ while(head<tail&&(Y(list[head])-Y(list[head+1]))>=dis[i]*(X(list[head])-X(list[head+1]))) head++; dp[i]=dp[list[head]]+(pp[i]-pp[list[head]])*dis[i]-(a[i]-a[list[head]])+c[i]; while(head<tail&&(Y(list[tail-1])-Y(list[tail]))*(X(list[tail])-X(i))>=(Y(list[tail])-Y(i))*(X(list[tail-1])-X(list[tail]))) tail--; list[++tail]=i; } printf("%lld",dp[n]); return 0; } //有两个点超时了???? 试了试其他代码也是超市
1612:特别行动队
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1100000; const int INF=0x3fffffff; typedef long long LL; int n,a,b,c; int list[maxn]; LL dp[maxn],s[maxn]; LL sqr(LL x){ return x*x; } LL X(int i){ return s[i]; } LL Y(int i){ return dp[i]+a*sqr(s[i])-b*s[i]; } int main(){ scanf("%d %d %d %d",&n,&a,&b,&c); for(int i=1;i<=n;i++){ scanf("%d",&s[i]); s[i]+=s[i-1]; } int head=1,tail=1; list[0]=0; for(int i=1;i<=n;i++){ while(head<tail&&(Y(list[head])-Y(list[head+1]))<(2*a*s[i])*(X(list[head])-X(list[head+1]))) head++; dp[i]=dp[list[head]]+a*sqr(s[i]-s[list[head]])+b*(s[i]-s[list[head]])+c; while(head<tail&&(Y(list[tail-1])-Y(list[tail]))*(X(list[tail])-X(i))<(Y(list[tail])-Y(i))*(X(list[tail-1])-X(list[tail]))) tail--; list[++tail]=i; } printf("%lld",dp[n]); return 0; }
1613:打印文章
这个也是例题了
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define LL long long const int maxn=5e5+100; //这道题最重要的是怎么推出两个工厂之间的花费公式 LL s[maxn],f[maxn],list[maxn]; int n,m; LL X(int i){ return s[i]; } LL Y(int i){ return f[i]+s[i]*s[i]; } int main(){ while(scanf("%d %d",&n,&m)!=EOF){ s[0]=0; int x; for(int i=1;i<=n;i++){ scanf("%d",&x); s[i]=s[i-1]+x; } int head=1,tail=1; list[1]=0; f[0]=0; for(int i=1;i<=n;i++){ while(head<tail&&(Y(list[head])-Y(list[head+1]))>=2*s[i]*(X(list[head])-X(list[head+1]))) head++; f[i]=f[list[head]]+(s[i]-s[list[head]])*(s[i]-s[list[head]])+m; while(head<tail&&(Y(list[tail-1])-Y(list[tail]))*(X(list[tail])-X(i))>=(Y(list[tail])-Y(i))*((X(list[tail-1]))-X(list[tail]))) tail--; list[++tail]=i; } printf("%lld\n",f[n]); } return 0; }
1614:锯木厂选址
这道题的代价计算公式和前面的仓库建设一样,主要是怎么推的
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define LL long long LL f[210000],w[210000],d[210000],a[210000]; int list[210000]; LL Y(LL i) {return w[i]*d[i];} LL X(LL i) {return w[i];} int main() { int n; scanf("%d",&n); for (int i=1;i<=n;i++) { int x,y; scanf("%d%d",&x,&y); d[i+1]=d[i]+y; a[i+1]=a[i]+x*d[i]; w[i]=w[i-1]+x; } int head=1,tail=1; //list[1]=1; f[1]=a[n]-a[1]; LL ans=9223372036854775807; for (int i=1;i<=n;i++) { while (head+1<=tail&& (Y(list[head]) - Y(list[head+1])) >= d[i] * (X(list[head]) - X(list[head+1]))) head++; f[i]=w[list[head]]*d[list[head]]-a[list[head]]+(w[i]-w[list[head]])*d[i]-(a[i]-a[list[head]])+(w[n]-w[i])*d[n+1]-(a[n+1]-a[i]); while (head<=tail-1&& (Y(list[tail-1]) - Y(list[tail])) * (X(list[tail]) - X(i)) >= (Y(list[tail]) - Y(i)) * (X(list[tail-1]) - X(list[tail]))) tail--; list[++tail]=i; ans=min(ans,f[i]); } printf("%lld\n",ans); return 0; }