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;
}

 

posted @ 2020-09-12 19:58  kongbursi  阅读(230)  评论(0)    收藏  举报