1. 程式人生 > 實用技巧 >poj1821 Fence(dp,單調佇列優化)

poj1821 Fence(dp,單調佇列優化)

題意:

由k(1 <= K <= 100)個工人組成的團隊應油漆圍牆,其中包含N(1 <= N <= 16 000)個從左到右從1到N編號的木板。每個工人i(1 <= i <= K)應該坐在木板Si的前面,並且他只能噴塗一個緊湊的間隔(這意味著該間隔中的木板應該是連續的)。此間隔應包含Si木板。同樣,工人最多塗li個木板,每塗一塊木板他應得到Pi $(1 <= Pi <= 10000)。一塊木板最多隻能由一個工人塗油漆。所有數字Si應該是不同的。

作為團隊的負責人,您要確定每個工人的油漆間隔,但要知道總收入應為最大。總收入是工人個人收入的總和。

題解:

首先按照si從小到達排序

f[i][j]表示前i個工匠粉刷到第j塊木板時的最大報酬
如果第i個粉刷工不粉刷牆壁,那麼它的最優解dp[i][j]可以由 dp[i-1][j]或者 dp[i][j-1]得到
如果第i個粉刷工需要粉刷牆壁,那麼它必須粉刷第si個牆壁
然後我們就需要列舉以si為分界線,第i個工人需要向左邊和右邊粉刷幾個牆壁,那麼這就存在一個最優情況
肯定存在一個k滿足最優:dp[i][j]=dp[i-1][k]+(j-k)*pi 。我們只需要列舉(j-k)就可以處理掉
“需要向左邊和右邊粉刷幾個牆壁”這個問題(j-Li<=k<=Si-1,j>=Si)

化簡一下這個式子就變成了dp[i-1][k]-k*pi+j*pi


其中j*pi是一個常量,哦我們可以先不管它

對於dp[i-1][k]我們可以 維護一個單調遞減佇列來完成減少每次找dp[i-1][k]最優

其實能用單調佇列優化dp的問題很統一,都能化成dp[i]=max/min(f[k])+g[j]的形式,其中f[k]是隻和k有關的函式,
g[j]和k無關。
這樣的問題由於f[0]-f[j-1]的值可以用單調佇列儲存,每次更新取出隊首元素即可,所以可以省掉一維的列舉.

剛開始複雜度計算錯了,開始dfs暴力了,一直TLE。。。。。

程式碼:

#include<iostream>
#include<algorithm>
#include
<cstdio> #include<queue> #include<map> #include<vector> #include<cstring> #include<deque> using namespace std; const int mod=1e9+7; const int maxn=1e2+5; const int N=20000+5; #define mem(a) memset(a,0,sizeof(a)) int n,k,dp[maxn][N]; deque<int>r; struct shudui { int l,p,s; } que[maxn]; bool mmp(shudui x,shudui y) { return x.s<y.s; } //void dfs(int x,int last) //{ // if(x>k) // { // if(dp[k]==16) // { // for(int i=1;i<=k;++i) // printf("%d ",dp[i]); // printf("\n"); // } // return; // } // int temp=que[x+1].s-last; // for(int i=1; i<=min(temp,que[x].l); ++i) // { // dp[x]=max(dp[x],dp[x-1]+i*que[x].p); // if(last+i<=que[x].s) dfs(x+1,que[x].s+1); // else dfs(x+1,last+i); // } //} //void dfs(int x,int last) //{ // if(x>k) // { // if(dp[k]==20) // { // for(int i=1;i<=k;++i) // printf("%d ",dp[i]); // printf("\n"); // } // return; // } // if(last>que[x].s) // { // dp[x]=dp[x-1]; // dfs(x+1,last); // } // else // { // int temp=(n+1)-last; // for(int i=1; i<=min(temp,que[x].l); ++i) // { // dp[x]=max(dp[x],dp[x-1]+i*que[x].p); // if(last+i<=que[x].s) dfs(x+1,que[x].s+1); // else dfs(x+1,last+i); // } // } // //} //f[i][j]=Pi*j+max{f[i-1][k]-P[i]*k} int oper(int i,int k) { return dp[i-1][k]-que[i].p*k; } int main() { scanf("%d%d",&n,&k); for(int i=1; i<=k; ++i) { scanf("%d%d%d",&que[i].l,&que[i].p,&que[i].s); } sort(que+1,que+1+k,mmp); //dfs(1,1); /* f[i][j]表示前i個工匠粉刷到第j塊木板時的最大報酬 如果第i個粉刷工不粉刷牆壁,那麼它的最優解dp[i][j]可以由 dp[i-1][j]或者 dp[i][j-1]得到 如果第i個粉刷工需要粉刷牆壁,那麼它必須粉刷第si個牆壁 然後我們就需要列舉以si為分界線,第i個工人需要向左邊和右邊粉刷幾個牆壁,那麼這就存在一個最優情況 肯定存在一個k滿足最優:dp[i][j]=dp[i-1][k]+(j-k)*pi 。我們只需要列舉(j-k)就可以處理掉 “需要向左邊和右邊粉刷幾個牆壁”這個問題(j-Li<=k<=Si-1,j>=Si) 化簡一下這個式子就變成了dp[i-1][k]-k*pi+j*pi 其中j*pi是一個常量,哦我們可以先不管它 對於dp[i-1][k]我們可以 維護一個單調遞減佇列來完成減少每次找dp[i-1][k]最優 其實能用單調佇列優化dp的問題很統一,都能化成dp[i]=max/min(f[k])+g[j]的形式,其中f[k]是隻和k有關的函式, g[j]和k無關。 這樣的問題由於f[0]-f[j-1]的值可以用單調佇列儲存,每次更新取出隊首元素即可,所以可以省掉一維的列舉. */ for(int i=1;i<=k;++i) { while(!r.empty()) r.pop_front(); for(int j=max(0,que[i].s-que[i].l);j<que[i].s;++j) { while(!r.empty() && oper(i,j)>=oper(i,r.back())) r.pop_back(); r.push_back(j); } for(int j=1;j<=n;++j) { dp[i][j]=max(dp[i-1][j],dp[i][j-1]); if(j>=que[i].s && j<que[i].s+que[i].l) { while(!r.empty() && j-que[i].l>r.front()) r.pop_front(); if(!r.empty()) dp[i][j]=max(dp[i][j],que[i].p*j+oper(i,r.front())); } } } printf("%d\n",dp[k][n]); return 0; }