【BZOJ2436】NOI嘉年华(动态规划)

94 篇文章 0 订阅
57 篇文章 0 订阅

题面

BZOJ

题解

考虑第一问如何求解
发现状态与选择了哪些活动无关,只与时间有关
f[i][j] f [ i ] [ j ] 表示前 i i 个单位时间(离散后),一个嘉年华选择了j个活动时
另外一个可以选择的最多的活动数量
转移的话枚举一下转移过来的时间 k k
考虑时间[k..i]的活动分配给哪个嘉年华就好了
所以 f[i][j]=max(f[k][j]+sum[k][i],f[k][jsum[k][i]]) f [ i ] [ j ] = m a x ( f [ k ] [ j ] + s u m [ k ] [ i ] , f [ k ] [ j − s u m [ k ] [ i ] ] )
其中 sum[i][j] s u m [ i ] [ j ] 表示时间 [i,j] [ i , j ] 中能够进行的所有活动的数量。
时间复杂度 O(n3) O ( n 3 )

考虑第二问
显然是中间一段强制选,然后剩下的地方被拆成了两段
然后考虑最大值。
注意,这强制选的一段不一定恰好是当前必须选的活动的这一段时间
而是可以向两边拓展
所以我们需要先预处理 dp[i][j] d p [ i ] [ j ] 表示时间 [i,j] [ i , j ] 的所有活动必须选择的最优值
g[i][j] g [ i ] [ j ] f f 表示相同的含义,但是时间不是[1,i]而是 [i,n] [ i , n ] ,即倒着选。
枚举前面和后面分割出来的时间某个嘉年华分别选择了几个活动,
这样很容易转移,相当于是把中间强制选择的那段时间的所有活动直接加给活动较少的那个嘉年华。
但是这样的复杂度是 O(n4) O ( n 4 ) 的。

我们需要优化这个转移,假设枚举选择了多少个活动的那个嘉年华
在强制选择的区间的前面选了 x x 个活动,后面选了y个活动
发现当 x x 增大时,另外一个嘉年华能够选择的个数会减小,
而中间强制选择的区间的值不会变化,如果继续增加y的话,发现另外一个嘉年华能够选择的值也会更小,所以 x x 增加时,y减少。
这样子可以利用单调性优化掉一个 n n ,时间复杂度变为O(n3)

每次回答询问的时候,确定当前强制选择的活动左右端点,暴力向左右拓展就好了。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define RG register
#define MAX 444
inline int read()
{
    RG int x=0,t=1;RG char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return x*t;
}
int L[MAX],R[MAX],S[MAX],tot,num[MAX][MAX];
int f[MAX][MAX],g[MAX][MAX],d[MAX][MAX],ans,n;
void cmax(int &x,int y){if(x<y)x=y;}
int Calc(int i,int j,int x,int y)
{
    if(f[i][x]<0||g[j][y]<0)return -1e9;
    int t=f[i][x]+g[j][y];
    return min(max(t,x+y),min(t,x+y)+num[i][j]);
}
int main()
{
    n=read();
    for(int i=1;i<=n;++i)L[i]=read(),R[i]=read()+L[i];
    for(int i=1;i<=n;++i)S[++tot]=L[i],S[++tot]=R[i];
    sort(&S[1],&S[tot+1]);tot=unique(&S[1],&S[tot+1])-S-1;
    for(int i=1;i<=n;++i)L[i]=lower_bound(&S[1],&S[tot+1],L[i])-S;
    for(int i=1;i<=n;++i)R[i]=lower_bound(&S[1],&S[tot+1],R[i])-S;
    for(int i=0;i<=tot+1;++i)
        for(int j=i;j<=tot+1;++j)
            for(int k=1;k<=n;++k)
                if(i<=L[k]&&R[k]<=j)++num[i][j];
    memset(f,-63,sizeof(f));f[0][0]=0;
    for(int i=1;i<=tot;++i)
        for(int j=0;j<=n;++j)
            for(int k=0;k<i;++k)
            {
                cmax(f[i][j],f[k][j]+num[k][i]);
                if(j>=num[k][j])cmax(f[i][j],f[k][j-num[k][i]]);
            }
    for(int i=1;i<=tot;++i)ans=max(ans,min(i,f[tot][i]));
    printf("%d\n",ans);
    memset(g,-63,sizeof(g));g[tot+1][0]=0;
    for(int i=tot;i;--i)
        for(int j=0;j<=n;++j)
            for(int k=i+1;k<=tot+1;++k)
            {
                cmax(g[i][j],g[k][j]+num[i][k]);
                if(j>=num[k][j])cmax(g[i][j],g[k][j-num[i][k]]);
            }
    for(int i=1;i<=tot;++i)
        for(int j=i;j<=tot;++j)
            if(num[i][j])
            {
                int y=n;
                for(int x=0;x<=n;++x)
                {
                    int nw=Calc(i,j,x,y);
                    while(y)
                    {
                        int nt=Calc(i,j,x,y-1);
                        if(nw<=nt)--y,nw=nt;
                        else break;
                    }
                    d[i][j]=max(d[i][j],nw);
                }
            }
    for(int i=1;i<=n;++i)
    {
        ans=0;
        for(int j=1;j<=L[i];++j)
            for(int k=R[i];k<=tot;++k)
                ans=max(ans,d[j][k]);
        printf("%d\n",ans);
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值