[题解] bzoj 2436 Noi 2011 NOI嘉年华(DP)

- 传送门 -

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

2436: [Noi2011]Noi嘉年华

Time Limit: 10 Sec  Memory Limit: 128 MB
Submit: 705  Solved: 467
[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

  ### - 题意 -  把节目当成一根线段 [ s , t ].  从 n 条线段中取出若干条, 在不切断线段的情况下将取出的线段切成若干份, 再将这若干份线段集分为两份, 使少的一份分到线段最多.  考虑必须取第 1 ~ n 条线段的情况.    ### - 思路 -  先用离散化处理数据, 设 $l_{ij}$ 表示前 i 个点 A 取 j 条线段之后 B 的最大数量, $r_{ij}$ 表示 i 点到右端点 A 取 j 条线段之后 B 的最大数量, $c_{ij}$ 表示 i 到 j 中线段数量, $g_{ij}$ 表示 i 到 j 内线段全都被 B 取到时的答案 .    $l_{ij}$ = max{ max{ $l_{kj}+c_{ki}$, $l_{k j-c_{ki}}$ } 0<=k <=i }   (前者表示 A 在 [0,k] 范围内取 j 条时 B 的最大取值 加上 [k,i] 的线段数,    后者表示 A 在 [0,k] 范围内取 $j-c_{ki}$ 条, 再取 [k,i] 范围内全部 $c_{ki}$ 条, 此时 B 的最大取值.)    r同理.     $g_{ij}$ = max { min{ x+y, $l_{ix}+c_{ij}+r_{jy}$ } 0<=x,y<=n }    ( A 在 [0, i] 中取 x 条线段,[j, 右端] 中取 y 条.)  枚举 x, y 的复杂度 (加上枚举i,j) 是 O(n^4)的, 但我们考虑: 假设 x+y 是较小值, 那当 x 增加时 y 必定要减小, 这样我们就可以只枚举 x, 维护 y 单调递减就好了, 复杂度降至O(n^3).

 细节见代码.

 PS :
 依旧窃别人的AC代码, 但我竟然没窃成功!!!
 于是愉快地找了一下午不同最后发现只有数组顺序不同
 依旧问Dalao , Dalao J 里 J 气地 说:
 我一眼就看出来--
 是数组溢出
 ....咦???
 是的, 这份AC代码在运行时数组下标竟然钻出来一个负数!!!
 可是这玄学代码还过了!!!
 原因是该AC代码数组顺序没毛病而我的顺序太玄学所以挂了,于是在访问数组时加特判看数组下标是否出现负数...
  

- 代码 -

 

#include<cstdio>
#include<algorithm>
#include<cstring>
#define mem(a,b) memset(a,b,sizeof a)
#define FOR(i,a,b) for(int i = a; i <= b; i++)
#define PRE(i,a,b) for(int i = a; i >= b; i--)
using namespace std;

int l[405][205],r[405][205],g[405][405];
int s[205],t[205],c[405][405],a[405];
int m,n;

int main() {
	scanf("%d",&n);
	FOR(i,1,n) {
		scanf("%d%d",&s[i],&t[i]);
		t[i] += s[i];
		a[++m] = s[i]; a[++m] = t[i];
	}
	sort(a+1,a+m+1);
	m = unique(a+1,a+m+1)- a - 1;
	FOR(i,1,n)
		s[i] = lower_bound(a+1,a+m+1,s[i]) - a,
		t[i] = lower_bound(a+1,a+m+1,t[i]) - a; //离散化
	FOR(i,0,m) {
		FOR(j,1,n)
			if(s[j] >= i)
				c[i][t[j]]++; // 此时c[i][j]表示[i,j]中以j结尾的线段条数
		FOR(j,i+2,m)
			c[i][j] += c[i][j-1];	 // [i,j]中线段条数
	}
	mem(l,128); mem(r,128); mem(g,128);
	l[0][0] = 0;
	FOR(i,1,m) FOR(j,0,n)
		FOR(k,0,i-1) {
			l[i][j] = max(l[i][j],l[k][j]+c[k][i]);
			if(j-c[k][i] >= 0) l[i][j] = max(l[i][j],l[k][j-c[k][i]]); //防止下标出现负数
	}
	r[m][0] = 0;
	PRE(i,m-1,0) FOR(j,0,n)
		PRE(k,m,i+1) {
			r[i][j] = max(r[i][j],r[k][j]+c[i][k]);
			if(j-c[i][k] >= 0) r[i][j] = max(r[i][j],r[k][j-c[i][k]]);
	}
	

	FOR(i,0,m) FOR(j,i+1,m) {
		for(int x = 0,y = n; x <= n; x++) {
			while(y >= 0 && x + y > l[i][x] + c[i][j] + r[j][y]) y--; // 保证x+y为较小值, 当 x 增大时, 另一方取到线段数明显减少, 故要减小y
			if(y >= 0) g[i][j] = max(g[i][j], x+y);
			else if(l[i][x]>=0 && r[j][y]>=0 ) g[i][j] = max(g[i][j],l[i][x] + c[i][j] + r[j][y]);
		}
	}
	int ans = 0;
	FOR(i,0,n)
		ans = max(ans,min(l[m][i],i));
	printf("%d\n",ans);
	
	FOR(i,1,n) {
		ans = 0;
		FOR(x,0,s[i]) FOR(y,t[i],m)
		ans = max(ans,g[x][y]);
		printf("%d\n",ans);
	}
	return 0;
}   
posted @ 2017-07-10 21:00  lstttt  阅读(106)  评论(0编辑  收藏  举报