[NOI2011]Noi嘉年华(动态规划及单调性优化)

【题解】

这道题不是按"第i个活动在哪个嘉年华举办"来进行决策的,而是利用题目"嘉年华A与B的活动时间无交叉"的性质,在离散化时间的基础上表示状态:

pre[i][j]表示:时间[1,i]中有j个活动在嘉年华A举办时,嘉年华B举办的最大活动数 

状态转移:先预处理得num[i][j]:离散化后时间满足i<=s<s+t<=j的活动数量 
          pre[i][j]=max{ pre[k][j]+num[k][i] , pre[k][j-num[k][i]] } ,1<=k<i (分为前i时间内举办完的最后一项活动在B中还是A中两种情况)

第一问的答案就是:max{ min(i,pre[T][i]) },T为离散化后的最大时间 

对于第二问,假设必须举办的活动在嘉年华A,且它的时间段包含于嘉年华A中的一段连续时间[i,j](可能与A中别的活动时间重叠),最大答案用ans[i][j]表示 
只需考虑 时间[1,i]与[j,T]的活动举办方式即可,而时间[1,i]中的所有情况,已经用pre[i][j]预处理过了 
那么同理,用suc[i][j]表示:时间[i,T]中有j个活动在嘉年华A举办时,嘉年华B举办的最大活动数 
则 suc[i][j]=max{ suc[k][j]+num[i][k] , suc[k][j-num[i][k]] } ,i<k<=T
预处理出所有ans[i][j]即可,ans[i][j]=max{ min( x+y+num[i][j] , pre[i][x]+suc[j][y] ) },需要枚举时间[1,i]与[j,T]内,在A中举办的活动数x,y

这样复杂度为O(n^4),优化:利用单调性 
对于确定的i,j,x越大,使min( x+y+num[i][j] , pre[i][x]+suc[j][y] )取最大值的y越小 
证明:反证法证明x增大时,使min最大的y不增,注意x增大前,这个y对于x是最优的,然后分情况:x增大前 左>=右 和 左<右 分别证明 
因此对y的枚举可省略 

询问时,输出 max{ ans[i][j] },[i,j]包含必选活动的时间 

注意:利用y的单峰性求ans[i][j]的这条语句:
while(y>0&&min(x+y+num[i][j],pre[i][x]+suc[j][y])<=min(x+y-1+num[i][j],pre[i][x]+suc[j][y-1])) y--;
其中"<="不能换成"<",理由如图【坑爹啊!!!】
    _(y3)_
   /           \
_/             \____(y1)___(y2)
y1<y2,且函数值相等,当y==y2时,若y不左移,就没有机会达到当前x对应的y3。而x加1后,峰值变化,不一定能取到最优值了 

所以,用<延迟更新是错误的 


【代码】

#include<stdio.h>
#include<stdlib.h>
#define INF 1000000
int num[405][405]={0},ans[405][405]={0},pre[405][205]={0},suc[405][205]={0},s[205]={0},t[205]={0},a[405]={0},c[405]={0};
int min(int a,int b)
{
	if(a<b) return a;
	return b;
}
int max(int a,int b)
{
	if(a>b) return a;
	return b;
}
void jh(int* a,int* b)
{
	int t=*a;
	*a=*b;
	*b=t;
}
void kp(int low,int high)//快排 
{
	int i=low,j=high,mid=a[(i+j)/2];
	while(i<j)
	{
		while(a[i]<mid) i++;
		while(a[j]>mid) j--;
		if(i<=j)
		{
			jh(&a[i],&a[j]);
			jh(&c[i],&c[j]);
			i++;
			j--;
		}
	}
	if(j>low) kp(low,j);
	if(i<high) kp(i,high);
}
int main()
{
	int n,T=0,i,j,k,x,y,Ans=0;
	scanf("%d",&n);
	for(i=1;i<=n;i++)
	{
		scanf("%d%d",&s[i],&t[i]);
		t[i]+=s[i];
		a[2*i-1]=s[i];
		a[2*i]=t[i];
	}
	for(i=1;i<=2*n;i++)
		c[i]=i;
	kp(1,2*n);
	for(i=1;i<=2*n;i++)
	{
		if(i==1||a[i]!=a[i-1]) T++;//离散化后时间从1开始 
		if(c[i]%2==1) s[c[i]/2+1]=T;
		else t[c[i]/2]=T;
	}
	for(i=1;i<=n;i++)
		for(j=1;j<=s[i];j++)
			for(k=t[i];k<=T;k++)
				num[j][k]++;
	for(i=1;i<=T;i++)
		for(j=1;j<=n;j++)
			pre[i][j]=suc[i][j]=-INF;
	for(i=1;i<=T;i++)
		for(j=0;j<=n;j++)
			for(k=1;k<i;k++)
			{
				pre[i][j]=max(pre[i][j],pre[k][j]+num[k][i]);
				if(j>=num[k][i]) pre[i][j]=max(pre[i][j],pre[k][j-num[k][i]]);
			}
	for(i=T;i>=1;i--)
		for(j=0;j<=n;j++)
			for(k=i+1;k<=T;k++)
			{
				suc[i][j]=max(suc[i][j],suc[k][j]+num[i][k]);
				if(j>=num[i][k])suc[i][j]=max(suc[i][j],suc[k][j-num[i][k]]);
			}
	for(i=0;i<=n;i++)
		Ans=max(Ans,min(i,pre[T][i]));
	printf("%d\n",Ans);
	for(i=1;i<T;i++)
		for(j=i+1;j<=T;j++)
		{
			y=num[j][T];
			for(x=0;x<=num[1][i];x++)
			{
				while(y>0&&min(x+y+num[i][j],pre[i][x]+suc[j][y])<=min(x+y-1+num[i][j],pre[i][x]+suc[j][y-1])) y--;
				ans[i][j]=max(ans[i][j],min(x+y+num[i][j],pre[i][x]+suc[j][y]));
			}
		}
	for(i=1;i<=n;i++)
	{
		Ans=0;
		for(j=1;j<=s[i];j++)
			for(k=t[i];k<=T;k++)
				Ans=max(Ans,ans[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、付费专栏及课程。

余额充值