[BZOJ]2436 [NOI2011] Noi嘉年华 DP

2436: [Noi2011]Noi嘉年华

Time Limit: 10 Sec   Memory Limit: 128 MB
Submit: 748   Solved: 492
[ Submit][ Status][ Discuss]

Description

NOI2011 在吉林大学开始啦!为了迎接来自全国各地最优秀的信息学选手,
吉林大学决定举办两场盛大的 NOI 嘉年华活动,分在两个不同的地点举办。每
个嘉年华可能包含很多个活动,而每个活动只能在一个嘉年华中举办。 
现在嘉年华活动的组织者小安一共收到了 n个活动的举办申请,其中第 i 个
活动的起始时间为 Si,活动的持续时间为Ti。这些活动都可以安排到任意一个嘉
年华的会场,也可以不安排。 
小安通过广泛的调查发现,如果某个时刻,两个嘉年华会场同时有活动在进
行(不包括活动的开始瞬间和结束瞬间),那么有的选手就会纠结于到底去哪个
会场,从而变得不开心。所以,为了避免这样不开心的事情发生,小安要求不能
有两个活动在两个会场同时进行(同一会场内的活动可以任意进行)。 
另外,可以想象,如果某一个嘉年华会场的活动太少,那么这个嘉年华的吸
引力就会不足,容易导致场面冷清。所以小安希望通过合理的安排,使得活动相
对较少的嘉年华的活动数量最大。 
此外,有一些活动非常有意义,小安希望能举办,他希望知道,如果第i 个
活动必须举办(可以安排在两场嘉年华中的任何一个),活动相对较少的嘉年华
的活动数量的最大值。

Input

 

输入的第一行包含一个整数 n,表示申请的活动个数。 
接下来 n 行描述所有活动,其中第 i 行包含两个整数 Si、Ti,表示第 i 个活
动从时刻Si开始,持续 Ti的时间。

Output


输出的第一行包含一个整数,表示在没有任何限制的情况下,活动较少的嘉
年华的活动数的最大值。 
接下来 n 行每行一个整数,其中第 i 行的整数表示在必须选择第 i 个活动的
前提下,活动较少的嘉年华的活动数的最大值。

 

Sample Input


5
8 2
1 5
5 3
3 2
5 3

Sample Output

2
2
1
2
2
2

HINT


在没有任何限制的情况下,最优安排可以在一个嘉年华安排活动 1, 4,而在

另一个嘉年华安排活动 3, 5,活动2不安排。


1≤n≤200 0≤Si≤10^9

 

1≤Ti≤ 10^9

 

Source

[ Submit][ Status][ Discuss]


HOME Back

这道题真的难....之前考试的时候一开始拿到这道题的时候以为是二分图, 后面觉得不对应该是网络流, 看了下数据范围正好合适....然而半个小时过去并没有建出来图. 觉得那就只有可能是n^3的算法... 那就DP吧, 设了好几个状态都搞不出来, 想了半天终究放弃.

没想到正解居然有4个DP数组(有一个只起维护作用). 我平常做DP最多就用到两个, 这次一共用4个...果然自己还是太弱了, DP什么的还得再练练. 

以下引自whjpji的题解:

首先将所有的区间离散化,这一步很好实现。 
然后就是求三个数组 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

#include<stdio.h>
#include<cstring>
#include<algorithm>
#define loss(a) memset(a, -10, sizeof(a))
using namespace std;
const int maxn = 410;
struct point{int l, r;}a[maxn];
int n, cnt, top, ans, q[maxn];
int f[maxn][maxn], c[maxn][maxn], g[maxn][maxn], num[maxn][maxn];
int main(){
    scanf("%d", &n);
    loss(f), loss(g), loss(c);
    for(int i = 1; i <= n; ++i){
        scanf("%d%d", &a[i].l, &a[i].r);
        a[i].r += a[i].l;
        q[++cnt] = a[i].l, q[++cnt] = a[i].r;
    }
    sort(q + 1, q + cnt + 1);
    top = unique(q + 1, q + cnt + 1) - q - 1;
    for(int i = 1; i <= n; ++i){
        a[i].l = lower_bound(q + 1, q + top + 1, a[i].l) - q;
        a[i].r = lower_bound(q + 1, q + top + 1, a[i].r) - q;
    }
    for(int i = 1; i <= top; ++i){
        for(int j = 1; j <= n; ++j)
            if(a[j].l >= i) ++num[i][a[j].r];
        for(int j = i + 1; j <= top; ++j)
            num[i][j] += num[i][j - 1];
    }
    f[1][0] = num[1][1], f[1][num[1][1]] = 0;
    for(int i = 2; i <= top; ++i)
        for(int j = 0; j <= n; ++j)
            for(int k = 1; k < i; ++k){
                f[i][j] = max(f[i][j], max(f[k][j] + num[k][i], f[k][j - num[k][i]]));
    }
    for(int i = 0; i <= n; ++i) ans = max(ans, min(i, f[top][i]));
    printf("%d\n", ans);
    g[top][0] = num[top][top], g[top][num[top][top]] = 0;
    for(int i = top - 1; i; --i)
        for(int j = 0; j <= n; ++j)
            for(int k = i + 1; k <= top; ++k)
                g[i][j] = max(g[i][j], max(g[k][j] + num[i][k], g[k][j - num[i][k]]));
    for(int i = 1; i <= top; ++i)
        for(int j = i; j <= top; ++j){
            for(int x = 0, y = n; x <= n; ++x){
                while(y >= 0 && x + y > num[i][j] + f[i][x] + g[j][y]) y--;
                if(y >= 0) c[i][j] = max(c[i][j], x + y);
            }
        }
    for(int i = 1; i <= n; ++i){
        ans=0;
        for(int j = 1; j <= a[i].l; ++j)
            for(int k = a[i].r; k<=top; ++k)
                ans=max(ans, c[j][k]);
        printf("%d\n",ans);
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值