【BZOJ2436】【NOI2011】—NOI嘉年华(dp)

传送门

发现这要把一段的活动归到一边去

先离散化时间
n u m [ l ] [ r ] num[l][r] num[l][r]表示全部在 [ l , r ] [l,r] [l,r]内的活动的个数

p r e [ i ] [ j ] pre[i][j] pre[i][j]表示前 i i i的时间内给一边 j j j个另一边最多有多少个
p r e [ i ] [ j ] = M a x k m a x ( p r e [ k ] [ j ] + n u m [ k ] [ i ] , p r e [ k ] [ j − n u m [ k ] [ i ] ] ) pre[i][j]=Max_{k}max(pre[k][j]+num[k][i],pre[k][j-num[k][i]]) pre[i][j]=Maxkmax(pre[k][j]+num[k][i],pre[k][jnum[k][i]])(就是分给哪边的情况)

第一问答案就是 M a x k m i n ( p r e [ t i m e ] [ k ] . k ) Max_{k}min(pre[time][k].k) Maxkmin(pre[time][k].k)

至于第二问,我们相当于钦点了一段区间 s [ i ] , t [ i ] s[i],t[i] s[i],t[i]必须选,设必须选 l , r l,r l,r的答案为 f [ l ] [ r ] f[l][r] f[l][r]
考虑类似于 p r e pre pre,处理出一个 s u f [ i ] [ j ] suf[i][j] suf[i][j]表示 i i i~ t i m e time time的时间内给一边 j j j个另一边最多多少个

那我们可以直接枚举给左右一边分别多少个
f [ l ] [ r ] = M a x x , y m i n ( p r e [ l ] [ x ] + n u m [ l ] [ r ] + s u f [ r ] [ y ] , x + y ) f[l][r]=Max_{x,y}min(pre[l][x]+num[l][r]+suf[r][y],x+y) f[l][r]=Maxx,ymin(pre[l][x]+num[l][r]+suf[r][y],x+y)

但有一个问题
我们处理出的 n u m num num表示的是全部在一段区间内的活动
而实际上有可能出现某一个活动,实际上在某一边里面
但由于时间跨越了我们的 l , r l,r l,r,没有被计算到内
所以实际上 a n s i = M a x l , r f [ l ] [ r ] ans_i=Max_{l,r}f[l][r] ansi=Maxl,rf[l][r]

但这样复杂度是 O ( n 4 ) O(n^4) O(n4)
考虑优化

发现在计算单个 l , r l,r l,r时, x x x增大, y y y总不会变大
因为 p r e 和 s u f pre和suf presuf都是单调递减的
而如果 p r e pre pre变小了, s u f suf suf也随着变小肯定不会变的更优
如果 p r e + s u f + n u m pre+suf+num pre+suf+num为较小值的话这样就会变得更小,不优
如果 x + y x+y x+y为较小值的也会变得更小,也不优

所以枚举 x x x的时候, y y y直接从大变小就可以了
复杂度 O ( n 3 ) O(n^3) O(n3)

#include<bits/stdc++.h>
using namespace std;
const int RLEN=1<<17|1;
inline char gc(){
	static char ibuf[RLEN],*ib,*ob;
	(ib==ob)&&(ob=(ib=ibuf)+fread(ibuf,1,RLEN,stdin));
	return (ib==ob)?EOF:*ib++;
}
inline int read(){
	char ch=getchar();
	int res=0,f=1;
	while(!isdigit(ch))f^=(ch=='-'),ch=getchar();
	while(isdigit(ch))res=(res+(res<<2)<<1)+(ch^48),ch=getchar();
	return res*f;
}
const int N=405;
int n,s[N],t[N],a[N],cnt,pre[N][N],suf[N][N],f[N][N],num[N][N];
inline void chemx(int &a,int b){
	a=a>b?a:b;
}
inline void chemn(int &a,int b){
	a=a>b?b:a;
}
#define calc(a,b) (min((a+b),(pre[l][a]+num[l][r]+suf[r][b])))
int main(){
	n=read();
	for(int i=1;i<=n;i++)s[i]=read(),a[++cnt]=s[i],t[i]=read()+s[i],a[++cnt]=t[i];
	sort(a+1,a+cnt+1);
	cnt=unique(a+1,a+cnt+1)-a-1;
	for(int i=1;i<=n;i++){
		s[i]=lower_bound(a+1,a+cnt+1,s[i])-a;
		t[i]=lower_bound(a+1,a+cnt+1,t[i])-a;
		for(int l=1;l<=s[i];l++)
			for(int r=t[i];r<=cnt;r++)num[l][r]++;
	}
	for(int i=1;i<=cnt;i++)
		for(int j=1;j<=n;j++)pre[i][j]=suf[i][j]=-1e9;
	for(int i=1;i<=cnt;i++)
		for(int j=0;j<=num[1][i];j++)
			for(int k=1;k<=i;k++){
				chemx(pre[i][j],pre[k][j]+num[k][i]);
				if(j>=num[k][i])chemx(pre[i][j],pre[k][j-num[k][i]]);
			}
	for(int i=cnt;i;i--)
		for(int j=0;j<=num[i][cnt];j++)
			for(int k=cnt;k>=i;k--){
				chemx(suf[i][j],suf[k][j]+num[i][k]);
				if(j>=num[i][k])chemx(suf[i][j],max(suf[k][j]+num[i][k],suf[k][j-num[i][k]]));
			}
	for(int l=1;l<=cnt;l++){
		for(int r=l;r<=cnt;r++){
			for(int x=0,y=num[r][cnt];x<=num[1][l];x++){
				while(y&&calc(x,y)<=calc(x,y-1))y--;
				chemx(f[l][r],calc(x,y));
			}
		}
	}
	int ans=0;
	for(int i=1;i<=cnt;i++)for(int j=1;j<=num[1][i];j++)chemx(ans,min(pre[i][j],j));
	cout<<ans<<'\n';
	for(int i=1;i<=n;i++){
		int res=0;
		for(int l=s[i];l;l--)
			for(int r=t[i];r<=cnt;r++)
			chemx(res,f[l][r]);
		cout<<res<<'\n';
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 有一个 $n$ 个点的棋盘,每个点上有一个数字 $a_i$,你需要从 $(1,1)$ 走到 $(n,n)$,每次只能往右或往下走,每个格子只能经过一次,路径上的数字和为 $S$。定义一个点 $(x,y)$ 的权值为 $a_x+a_y$,求所有满足条件的路径中,所有点的权值和的最小值。 输入格式 第一行一个整数 $n$。 接下来 $n$ 行,每行 $n$ 个整数,表示棋盘上每个点的数字。 输出格式 输出一个整数,表示所有满足条件的路径中,所有点的权值和的最小值。 数据范围 $1\leq n\leq 300$ 输入样例 3 1 2 3 4 5 6 7 8 9 输出样例 25 算法1 (树形dp) $O(n^3)$ 我们可以先将所有点的权值求出来,然后将其看作是一个有权值的图,问题就转化为了在这个图中求从 $(1,1)$ 到 $(n,n)$ 的所有路径中,所有点的权值和的最小值。 我们可以使用树形dp来解决这个问题,具体来说,我们可以将这个图看作是一棵树,每个点的父节点是它的前驱或者后继,然后我们从根节点开始,依次向下遍历,对于每个节点,我们可以考虑它的两个儿子,如果它的两个儿子都被遍历过了,那么我们就可以计算出从它的左儿子到它的右儿子的路径中,所有点的权值和的最小值,然后再将这个值加上当前节点的权值,就可以得到从根节点到当前节点的路径中,所有点的权值和的最小值。 时间复杂度 树形dp的时间复杂度是 $O(n^3)$。 C++ 代码 算法2 (动态规划) $O(n^3)$ 我们可以使用动态规划来解决这个问题,具体来说,我们可以定义 $f(i,j,s)$ 表示从 $(1,1)$ 到 $(i,j)$ 的所有路径中,所有点的权值和为 $s$ 的最小值,那么我们就可以得到如下的状态转移方程: $$ f(i,j,s)=\min\{f(i-1,j,s-a_{i,j}),f(i,j-1,s-a_{i,j})\} $$ 其中 $a_{i,j}$ 表示点 $(i,j)$ 的权值。 时间复杂度 动态规划的时间复杂度是 $O(n^3)$。 C++ 代码

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值