P4182 [USACO18JAN] Lifeguards P
提供一个比较优秀大常数的时间 ,空间线性的做法。由于变量名冲突,本文中 均指题目中的 。
推推性质,发现若区间包含了另一个区间,则一定删掉被包含的区间,正确性显然。这样我们得到了一些左右端点都递增的线段。
将端点从小到大排序,很自然的设 表示前 个线段扔了 个,并且强制选择 保留,容易列出转移方程:
可以 枚举 ,复杂度 。
观察上式,发现可以分成两个部分。
把 抽象成一个二维的表,发现 的决策集合是第零行的一段前缀与一条在它左边一个单位的斜线。
上图中打红叉的格子是当前的 ,黄色的格子是决策集合。
所以我们可以枚举橙色格子的位置,每次处理一条向右下的斜线的所有格子 DP 值这样决策集合是单增不减的,对于第零行首先预处理出 DP 值(第一行表示一个都不删,随便 DP),然后额外维护。
但是现在的问题是如何处理式子右边的两个 ,记 表示编号最小的和 有重叠部分的线段,这样我们可以把 再拆开。
注意边界特判。继续观察式子,发现我们需要维护:
-
第 行的前缀的 的最大值。
-
斜线上的前缀 的最大值。
-
第 行的区间 的最大值。
-
斜线上区间 的最大值。
前两个是好维护的,第四个由于 单增,决策集合右端点和左端点都只增不减,所以可以单调队列,但是第三个我们似乎没有好的处理方式,好像只能 ST 表或者线段树之类的,但是这样除非用一些科技,时间或空间会多一只 ,我们想要更优秀和更简单的解决方式。
继续观察第三个式子。一条斜线上只有 个点需要 DP,但是注意查询区间的长度是没有限制的,可能许多点的 都远远小于它本身,只简单的在第 行维护一个单调队列复杂度是假的,最差仍然是 。
但是发现所以左端点最多只有 个,所以我们可以记录哪些点是其他点的 ,然后把打了标记的点之间的 的权值都归到左边的标记点上。这样一条斜线的左端点数就是 的级别了。我们只需要在第 行维护另外一个单调队列,每次对于一条斜线预处理出它要查询的位置到当前橙色格子的 (点数也是 级别的),这样复杂度就是对的。
代码细节很多,很不好写。
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');
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现