1. 程式人生 > >Vijos1243:生產產品(單調佇列優化dp)

Vijos1243:生產產品(單調佇列優化dp)

傳送門

題意:
n 個任務,m 個機器,每個機器完成每個任務都有特定的時間,任務必須依次完成,且一個機器只能連續完成 l 個任務,每次更換機器需要時間 k ,求完成所有任務的最短時間。
(n100000,m5)
題解:
首先,sum[i][j]表示第i臺機器完成前j項任務所需的時間,f[i][j]表示完成了前i項任務,且第i項任務由第j臺機器完成的最小時間,很容易寫出轉移方程:

f[i][j]=minx=ili1f[x][p]+sum[j][i]sum[j][x](pj)

但是,這樣轉移的時間複雜度為O(nm2l)

考慮優化,進一步發現n很小,想辦法從n下手優化:
每一次轉移如果 j

確定,找的是min(f[x][p]sum[j][x])。可以建立n2個關於x的單調佇列,且保證x,(f[x][p]sum[j][x])單調遞增。每次取隊首更新即可。選取出的最優解再按單調佇列的規則去更新其他單調佇列。時間複雜度O(nm2)

需要注意的一點是,要先把所有dp值算出來之後再一一更新。

#include<bits/stdc++.h>
using namespace std;
const int Maxn=6,Maxm=1e5+50;
typedef long long ll;
const ll INF=0x3f3f3f3f3f3f3f3f;
int m,n,k,l;
ll sum[Maxn][Maxm],ans=INF;
struct
node { int pos;ll val; node(int pos=0,ll val=0):pos(pos),val(val){} }; int head[Maxn][Maxn],tail[Maxn][Maxn]; node q[Maxn][Maxn][Maxm]; ll f[Maxm][Maxn]; int main() { scanf("%d%d%d%d",&m,&n,&k,&l); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) { scanf
("%lld",&sum[i][j]); sum[i][j]+=sum[i][j-1]; } if(n==1) { printf("%lld\n",sum[1][m]); return 0; } for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) q[i][j][head[i][j]=tail[i][j]=0]=node(0,-k); for(int t=1;t<=m;t++) { for(int i=1;i<=n;i++) { ll id=0; for(int j=1;j<=n;j++) { if(j==i)continue; while(q[i][j][head[i][j]].pos<t-l)head[i][j]++; if(!id||f[t][i]>q[i][j][head[i][j]].val)id=j,f[t][i]=q[i][j][head[i][j]].val; } f[t][i]+=((ll)k+sum[i][t]); if(t==m)ans=min(ans,f[t][i]); } for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { if(i==j)continue; ll tmp=f[t][i]-sum[j][t]; while(head[j][i]<=tail[j][i]&&q[j][i][tail[j][i]].val>=tmp)tail[j][i]--; q[j][i][++tail[j][i]]=node(t,tmp); } } } printf("%lld\n",ans); }