P4182 [USACO18JAN] Lifeguards P

P4182 [USACO18JAN] Lifeguards P

本文总字数:4360,阅读预计需要:11分钟

P4182 [USACO18JAN] Lifeguards P

更好的阅读体验

提供一个比较优秀大常数的时间 O(nm),空间线性的做法。由于变量名冲突,本文中 m 均指题目中的 k

推推性质,发现若区间包含了另一个区间,则一定删掉被包含的区间,正确性显然。这样我们得到了一些左右端点都递增的线段。

将端点从小到大排序,很自然的设 fi,j 表示前 i 个线段扔了 j 个,并且强制选择 i 保留,容易列出转移方程:

fi,j=maxk=0i1(fk,max(0,j(ik1))+rimax(li,rk))

可以 O(n) 枚举 k,复杂度 O(n2m)

观察上式,发现可以分成两个部分。

fi,j={fk,0+rimax(li,rk)kij1fk,ji+k1+rimax(li,rk)k>ij1

f 抽象成一个二维的表,发现 fi,j 的决策集合是第零行的一段前缀与一条在它左边一个单位的斜线。

image.png

上图中打红叉的格子是当前的 fi,j,黄色的格子是决策集合。

所以我们可以枚举橙色格子的位置,每次处理一条向右下的斜线的所有格子 DP 值这样决策集合是单增不减的,对于第零行首先预处理出 DP 值(第一行表示一个都不删,随便 DP),然后额外维护。

但是现在的问题是如何处理式子右边的两个 max,记 prei 表示编号最小的和 i 有重叠部分的线段,这样我们可以把 max 再拆开。

fi,j={fk,0+rilik<min(prei,ij1)fk,0rk+ripreikij1fk,ji+k1+riliij1<k<preifk,ji+k1rk+rimax(prei,ij1)k<i

注意边界特判。继续观察式子,发现我们需要维护:

  1. 0 行的前缀的 f0,i 的最大值。

  2. 斜线上的前缀 fi,j 的最大值。

  3. 0 行的区间 firi 的最大值。

  4. 斜线上区间 fi,jri 的最大值。

前两个是好维护的,第四个由于 prei 单增,决策集合右端点和左端点都只增不减,所以可以单调队列,但是第三个我们似乎没有好的处理方式,好像只能 ST 表或者线段树之类的,但是这样除非用一些科技,时间或空间会多一只 log,我们想要更优秀和更简单的解决方式。

继续观察第三个式子。一条斜线上只有 m 个点需要 DP,但是注意查询区间的长度是没有限制的,可能许多点的 pre 都远远小于它本身,只简单的在第 0 行维护一个单调队列复杂度是假的,最差仍然是 O(n2m)

但是发现所以左端点最多只有 m,所以我们可以记录哪些点是其他点的 pre,然后把打了标记的点之间的 f0,iri 的权值都归到左边的标记点上。这样一条斜线的左端点数就是 O(m) 的级别了。我们只需要在第 0 行维护另外一个单调队列,每次对于一条斜线预处理出它要查询的位置到当前橙色格子的 maxf0,iri(点数也是 O(m) 级别的),这样复杂度就是对的。

代码细节很多,很不好写。

	int n,m,len,ans,cnt,now,from,real[100001],pre[100001],t[101],pos[100001],Val[100001],V[100001],vis[100001],h[100001],f[2][101];
	pii a[100001],b[100001];
	bool cmp(pii x,pii y){return x.se<y.se;}
	deque<int> Q,q;
	inline void mian()
	{
		read(n,m),from=1;
		for(int i=1;i<=n;++i)read(a[i]),--a[i].se;
		sort(a+1,a+1+n,cmp),b[0]=mp(-inf,-inf);
		for(int i=1;i<=n;++i)
		{
			while(b[len].fi>=a[i].fi)--len;
			b[++len]=a[i];
		}
		m=max(0,m-(n-len)),n=len,memset(Val,128,sizeof(Val)),memset(f,128,sizeof(f)),h[0]=len=0;
		for(int i=1,j=0;i<=n;vis[pre[i++]=j+1]=1)while(b[j+1].se<b[i].fi)++j;
		for(int i=1;i<=n;++i)h[i]=h[i-1]+b[i].se-max(b[i-1].se,b[i].fi-1);
		for(int i=n,maxn=h[n]-b[n].se;i>=1;--i,maxn=max(maxn,h[i]-b[i].se))if(vis[i])V[pos[i]=++len]=maxn,real[len]=i,maxn=-inf;
		for(int i=1;i<=n;++i)if(pos[i])pos[i]=len-pos[i]+1;
		reverse(V+1,V+1+len),reverse(real+1,real+1+len);
		if(n>=m)ans=h[n-m];
		for(int st=1,last=0;st<=n;++st)
		{
			while(!q.empty())q.pop_back();
			now^=1,from^=1;int r=min(last,pos[pre[st+m]]);
			while(!Q.empty()&&Q.front()<real[r])Q.pop_front();
			if(Q.size())
			Val[r]=h[Q.front()]-b[Q.front()].se;
			else
			Val[r]=-inf;
			for(int i=r-1;i>=pos[pre[st]];--i)
			Val[i]=max(Val[i+1],V[i]);
			for(int i=st+1,j=1,maxn=0,i1=i-2,j1=0;i<=n&&j<=m;++i,++j)
			{
				f[now][j]=h[min(st-1,max(0,pre[i]-1))]+b[i].se-b[i].fi+1;
				f[now][j]=max(f[now][j],b[i].se+Val[pos[pre[i]]]);
				while(i1+1<pre[i]&&i1+1<i&&j1+1<=j)maxn=max(maxn,f[from][++j1]),++i1;
				f[now][j]=max(f[now][j],maxn+b[i].se-b[i].fi+1);
				while(!q.empty()&&f[from][q.back()]-b[q.back()+st-1].se<=f[from][j]-b[i-1].se)q.pop_back();
				q.eb(j);
				while(!q.empty()&&q.front()+st-1<pre[i])q.pop_front();
				if(!q.empty())f[now][j]=max(f[now][j],f[from][q.front()]+b[i].se-b[q.front()+st-1].se);
				if(j+n-i>=m)ans=max(ans,f[now][j]);
			}
			if(vis[st])last=pos[st];
			for(int i=r;i>=pos[pre[st]];--i)Val[i]=-inf;
			while(!Q.empty()&&h[Q.back()]-b[Q.back()].se<=h[st]-b[st].se)Q.pop_back();
			Q.eb(st);
		}
		write(ans,'\n');
	}
posted @   WrongAnswer_90  阅读(14)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现

This blog has running: 562 days 8 hours 45 minutes 19 seconds

点击右上角即可分享
微信分享提示