[BZOJ 2436][NOI 2011]NOI嘉年华(DP优化)

59 篇文章 0 订阅
31 篇文章 0 订阅

题目链接

http://www.lydsy.com/JudgeOnline/problem.php?id=2436

思路

这个题做起来很复杂很繁琐。。。以下思路整理自http://blog.csdn.net/whjpji/article/details/7547159
首先将所有的区间离散化,这一步很好实现。
然后就是求三个数组 num[i][j],pre[i][j],suf[i][j]
num[i][j]=[i,j] 区间内的线段个数。
pre[i][j]=[0,i] 区间内给B j 个线段,A得到最多线段个数。
suf[i][j]=[j,)区间内给B j 个线段,A得到最多线段个数。

容易推出num[i][j]。我们枚举 i ,对于所有左端点Lt大于等于 i 的区间右端点Rt,标记 num[i][Rt]++ ,那么显然 num[i][j]=jk=inum[i][k] ,这一个求和的步骤,可以将所有的 num[i][t] O(n) 时间内通过递推求前缀和的形式求出。

然后就是 pre[i][j]suf[i][j] 了,这两个其实基本上一样,下面只讲 pre[i][j] 的求法。

pre[i][j]=max{max0k<i{pre[k][j]+num[k][i],pre[k][jnum[k][i]]}}

(注:区间 [0,i] 中给B放入 j 个线段,可以枚举k<i,要么在区间 [0,k] 中给B放入 j 个线段,(k,i]这段里的所有线段都给A;要么在区间 [0,k] 中给B放入 jnum[j][k] 个线段, (k,i] 这段里的所有线段都给B)

然后就要求一个 g[i][j] g[i][j]=[i,j] 区间内的所有线段必须选,使得A和B中保含线段少的那个集合里的线段个数最多多少。

g[i][j]=maxx,y[0,n]{min{x+y,pre[i][x]+num[i][j]+suf[j][y]}}


fx,y=min{x+y,pre[i][x]+num[i][j]+suf[j][y]}

x [i,j]左边放入B中的线段个数, y [i,j]右边放入B中的线段个数

x 固定的情况下,fx,yy递增呈单凸特点,因此可以利用这一性质,维护两个指针 x,y ,遍历 x ,对于每个x,可以在期望复杂度 O(1) 时间内,移动指针 y 并快速找到使得当前的fx,y最大的 y <script id="MathJax-Element-774" type="math/tex">y</script>。

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 410
#define INF 0x3f3f3f3f

using namespace std;

struct Segment
{
    int L,R;
}seg[MAXN*2];

int stack[MAXN*2],top=0,n;
int pre[MAXN][MAXN],suf[MAXN][MAXN],g[MAXN][MAXN];
int num[MAXN][MAXN];

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&seg[i].L,&seg[i].R);
        seg[i].R+=seg[i].L; //!!!!
        stack[top++]=seg[i].L;
        stack[top++]=seg[i].R;
    }
    sort(stack,stack+top);
    top=unique(stack,stack+top)-stack;
    for(int i=1;i<=n;i++)
    {
        seg[i].L=lower_bound(stack,stack+top,seg[i].L)-stack;
        seg[i].R=lower_bound(stack,stack+top,seg[i].R)-stack;
    }
    for(int i=0;i<top;i++) //预处理出num[i][i]~num[i][top]
    {
        for(int j=1;j<=n;j++) //!!!!!
            if(seg[j].L>=i)
                num[i][seg[j].R]++;
        for(int j=i+1;j<top;j++) //!!!!!
            num[i][j]+=num[i][j-1];
    }
    memset(pre,~INF,sizeof(pre));
    memset(suf,~INF,sizeof(suf));
    pre[0][0]=0;
    suf[top-1][0]=0;
    for(int i=0;i<top;i++)
    {
        for(int j=0;j<=n;j++)
            if(pre[i][j]>~INF)
                pre[i][pre[i][j]]=max(pre[i][pre[i][j]],j);
        for(int j=n-1;j>=0;j--)
            pre[i][j]=max(pre[i][j],pre[i][j+1]);
        for(int j=0;j<=n;j++)
            for(int k=i+1;k<top;k++) //向后递推,[0,i]里给A放入j个区间,那么用[0,k]里在B中放入pre[i][j]个区间,在A中放入j+num[i][k]个区间的方案更新答案
                if(pre[i][j]>~INF)
                    pre[k][pre[i][j]]=max(pre[k][pre[i][j]],j+num[i][k]);
    }
    for(int i=top-1;i>=0;i--)
    {
        for(int j=0;j<=n;j++)
            if(suf[i][j]>~INF)
                suf[i][suf[i][j]]=max(suf[i][suf[i][j]],j);
        for(int j=n-1;j>=0;j--)
            suf[i][j]=max(suf[i][j],suf[i][j+1]);
        for(int j=0;j<=n;j++)
            for(int k=i-1;k>=0;k--) //向前递推,[i,INF)里给A放入j个区间,那么用[k,INF)里在B中放入suf[i][j]个区间,在A中放入j+num[k][i]个区间的方案来更新答案
                if(suf[i][j]>~INF)
                    suf[k][suf[i][j]]=max(suf[k][suf[i][j]],j+num[k][i]);
    }
    for(int i=0;i<top;i++) //求g[i][j]=必须使用[i,j]内的区间
        for(int j=i;j<top;j++)
        {
            g[i][j]=~INF;
            for(int x=0,y=n;x<=n;x++)
            {
                while(y>=0&&x+y>num[i][j]+pre[i][x]+suf[j][y]) y--; //x=[0,i]中B选择的区间个数,y=[j,INF)中B选择的区间个数
                if(y>=0) g[i][j]=max(g[i][j],x+y);
            }
        }
    int ans=0;
    for(int i=0;i<=n;i++)
        ans=max(ans,min(i,suf[0][i]));
    printf("%d\n",ans);
    for(int i=1;i<=n;i++) //!!!!
    {
        ans=0;
        for(int j=0;j<=seg[i].L;j++)
            for(int k=seg[i].R;k<top;k++)
                ans=max(ans,g[j][k]);
        printf("%d\n",ans);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值