bzoj2436: [Noi2011]Noi嘉年华

我震惊了,我好菜,我是不是该退役(苦逼)

 

可以先看看代码里的注释

首先我们先考虑一下第一问好了真做起来也就这个能想想了

那么离散化时间是肯定的,看一手范围猜出是二维DP,那对于两个会场,一个放自变量,一个放变量,然后O(n^3)的DP好了

第二问像第一问的做法特判一波就是O(n^4)啦

对于一个嘉年华必选,等价于必选一段区间,我们设f[l][r]为必选l,r放一起,前面一段自己处理,后面一段自己处理的最优解

那么ans=max(f[l][r]) (a[i].l<=l,r<=a[i].r)

可以发现前面一段自己处理我们在第一问已经搞定了。。。后面高仿前面就好。。。

f[l][r]=max(min(s[l-1][x]+h[l][r]+t[r+1][y],x+y)) 

然后这还是个四方的

但是用脑(bai)子(du)想想,t[r+1][y]随y增大减小,把min中两项写成两个一次函数,这个min的图像会是一个v字形(一个下降的直线和一个上升的直线),v的最下方就是决策点

当x增大的时候由于s[l-1][x]也跟着减小,相当于前一个截距变小,而后一个截距变大,决策点左移,所以是有决策单调性的y可以扫一遍完事

 

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxn=210;
const int maxT=2*maxn;

struct node{int l,r;}a[maxn];
int lslen,ls[maxT];

int h[maxT][maxT];//时间段内------->有多少嘉年华 
int s[maxT][maxn],t[maxT][maxn];//前/后到i的时间段,给第一个j,另一个最多混到多少 
int f[maxT][maxT];//这个时间段必选,且没有选择和该时间段相交的嘉年华------>较小的最大为多少 
int main()
{
    freopen("a.in","r",stdin);
    freopen("a.out","w",stdout);
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&a[i].l,&a[i].r);a[i].r+=a[i].l;
        a[i].l++;
        ls[++lslen]=a[i].l;
        ls[++lslen]=a[i].r;
    }
    sort(ls+1,ls+lslen+1);
    lslen=unique(ls+1,ls+lslen+1)-ls-1;
    for(int i=1;i<=n;i++)
        a[i].l=lower_bound(ls+1,ls+lslen+1,a[i].l)-ls,
        a[i].r=lower_bound(ls+1,ls+lslen+1,a[i].r)-ls;
        
    for(int l=1;l<=lslen;l++)
        for(int r=l;r<=lslen;r++)
            for(int i=1;i<=n;i++)
                if(l<=a[i].l&&a[i].r<=r)h[l][r]++;
                
    //.......init........
    
    memset(s,-31,sizeof(s));s[0][0]=0;
    for(int i=0;i<lslen;i++)
        for(int j=0;j<=n;j++)
            if(s[i][j]!=-1)
                for(int k=i+1;k<=lslen;k++)
                    s[k][j]=max(s[k][j],s[i][j]+h[i+1][k]),
                    s[k][j+h[i+1][k]]=max(s[k][j+h[i+1][k]],s[i][j]);
    int mx=0;
    for(int j=0;j<=n;j++)
        mx=max(mx,min(j,s[lslen][j]));
    printf("%d\n",mx);

    memset(t,-31,sizeof(t));t[lslen+1][0]=0;
    for(int i=lslen+1;i>1;i--)
        for(int j=0;j<=n;j++)
            if(t[i][j]!=-1)
                for(int k=i-1;k>=1;k--)
                    t[k][j]=max(t[k][j],t[i][j]+h[k][i-1]),
                    t[k][j+h[k][i-1]]=max(t[k][j+h[k][i-1]],t[i][j]);
        
    //......solve1.......
    
    for(int l=1;l<=lslen;l++)
        for(int r=l;r<=lslen;r++)
        {
            int y=n;
            for(int x=0;x<=n;x++)
            {
                if((x+y)<=n)
                    f[l][r]=max(f[l][r],min(s[l-1][x]+h[l][r]+t[r+1][y],x+y));
                while(y>=0&&((x+y)>n||s[l-1][x]+h[l][r]+t[r+1][y]<x+y))
                {
                    y--;
                    f[l][r]=max(f[l][r],min(s[l-1][x]+h[l][r]+t[r+1][y],x+y));
                }
            }
        }
    
    for(int i=1;i<=n;i++)
    {
        int ans=0;
        for(int l=1;l<=a[i].l;l++)
            for(int r=a[i].r;r<=lslen;r++)
                ans=max(ans,f[l][r]);
        printf("%d\n",ans);
    }
    
    return 0;
}

 

转载于:https://www.cnblogs.com/AKCqhzdy/p/10284820.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值