POJ 1821 Fence 單調佇列優化dp
Fence
Time Limit: 1000MS Memory Limit: 30000K
Total Submissions: 5189 Accepted: 1646
Description
A team of k (1 <= K <= 100) workers should paint a fence which contains N (1 <= N <= 16 000) planks numbered from 1 to N from left to right. Each worker i (1 <= i <= K) should sit in front of the plank Si and he may paint only a compact interval (this means that the planks from the interval should be consecutive). This interval should contain the Si plank. Also a worker should not paint more than Li planks and for each painted plank he should receive Pi $ (1 <= Pi <= 10 000). A plank should be painted by no more than one worker. All the numbers Si should be distinct.
Being the team’s leader you want to determine for each worker the interval that he should paint, knowing that the total income should be maximal. The total income represents the sum of the workers personal income.
Write a program that determines the total maximal income obtained by the K workers.
Input
The input contains:
Input
N K
L1 P1 S1
L2 P2 S2
…
LK PK SK
Semnification
N -the number of the planks; K ? the number of the workers
Li -the maximal number of planks that can be painted by worker i
Pi -the sum received by worker i for a painted plank
Si -the plank in front of which sits the worker i
Output
The output contains a single integer, the total maximal income.
Sample Input
8 4
3 2 2
3 2 3
3 3 5
1 1 7
Sample Output
17
Hint
Explanation of the sample:
the worker 1 paints the interval [1, 2];
the worker 2 paints the interval [3, 4];
the worker 3 paints the interval [5, 7];
the worker 4 does not paint any plank
Source
Romania OI 2002
題意:
有k個人刷長為n的木板,每個人可以選擇不刷,但如果要刷的話必須刷一個完整的區間,區間長度不能大於l[i]。且包含s[i]。每個人刷一塊木板可以獲得p[i]的收益,求最大的收益。
題解:
可以先寫出最樸素的dp方程:
定義dp[i][j]代表前i個人刷前j塊木板可以獲得的最大收益。
dp[i][j]=max(dp[i-1][j],dp[i][j-1]),即前i個人塗前j塊木板的最大收益為前i-1個人塗j塊(第i個人不塗)或者第j塊木板不塗的最大收益。
或者dp[i][j]=dp[i-1][k]+(j-k)*p[i],k< s[i]且k+l[i]>=j。
如果列舉i,j,k暴力轉移肯定是會TLE的,而考慮最後一個dp方程:
dp[i][j]=dp[i-1][k]+j*p[i]-k*p[i],我們會發現dp[i-1][k]-k*p[i]是一個與k無關的量,可以在之前就把它預處理出來,所以維護一個單調遞減的佇列,每次從隊首取出滿足條件的最大的k,O(1)轉移。
大概是第一道單調佇列的題。。(其實應該可以用資料結構來水的)
注意要滿足s比較小的先更新,所以先將人按s排一遍序。
樸素程式碼:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
return x*f;
}
const int K = 100 + 10;
const int N = 16000 + 10;
int n,k;
int dp[K][N];
struct node{
int l,p,s;
}a[K];
bool operator < (node a,node b){
return a.s<b.s;
}
int main(){
n=read(),k=read();
for(int i=1;i<=k;++i) a[i].l=read(),a[i].p=read(),a[i].s=read();
sort(a+1,a+k+1);
for(int i=1;i<=k;++i){
for(int j=1;j<=n;++j){
dp[i][j]=std::max(dp[i-1][j],dp[i][j-1]);
if(j<a[i].s||j>=a[i].s+a[i].l) continue;
for(int d=max(0,j-a[i].l);d<a[i].s;++d)
dp[i][j]=max(dp[i][j],dp[i-1][d]+(j-d)*a[i].p);
}
}
printf("%d\n",dp[k][n]);
return 0;
}
AC程式碼:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
return x*f;
}
const int K = 100 + 10;
const int N = 16000 + 10;
int n,k;
int dp[K][N];
struct node{
int l,p,s;
}a[K];
bool operator < (node a,node b){
return a.s<b.s;
}
deque<int> q;
int main(){
n=read(),k=read();
for(int i=1;i<=k;++i) a[i].l=read(),a[i].p=read(),a[i].s=read();
sort(a+1,a+k+1);
for(int i=1;i<=k;++i){
while(!q.empty()) q.pop_back();
q.push_back(std::max(a[i].s-a[i].l,0));
for(int j=1;j<=n;++j){
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
if(j>=a[i].s+a[i].l) continue;
while(!q.empty()&&q.front()<j-a[i].l) q.pop_front();
if(j<a[i].s){//符合條件 入隊
int x=dp[i-1][j]-a[i].p*j;
while(!q.empty()&&x>dp[i-1][q.back()]-a[i].p*q.back()) q.pop_back();
q.push_back(j);
continue;
}
dp[i][j]=max(dp[i][j],dp[i-1][q.front()]+(j-q.front())*a[i].p);
}
}
printf("%d\n",dp[k][n]);
return 0;
}