P1668 [USACO04DEC] Cleaning Shifts S 题解

题目传送门

前言

本题解的思路是贪心,但亮点在于无需排序,更好理解。

题目简述

给出 $n$($n\le 2.5\times 10^4$)个区间 $[l_i,r_i]$($l_i,r_i\le t\le10^6$),要求选出最少的区间使得可以覆盖区间 $[1,t]$。

思路

先不考虑无解的情况。

首先想到:如果两个区间 $[l,r],[l,r']$ 左端点相同,而 $r'<r$,我们显然可以舍弃区间 $[l,r']$,因为它可以被 $[l,r]$ 完全替代。

自然地,考虑存一个数组 $to_l$ 表示对于左端点 $l$,与之对应的所有 $r$ 的最大值。默认初始化为 $0$。它显然是很好维护的,在读入时取 max 即可:

for(int i=1,l,r;i<=n;i++)
{
scanf("%d %d",&l,&r);// 读入每个区间
to[l]=max(to[l],r);// 对于 l 求出最大的 r
}

又由于如果当前已经覆盖的区间 $[1,r]$ 右端点越大,下一步可以选择的新区间就越多,更容易大幅扩张已覆盖的区间,更可能取到最优解,所以考虑贪心,以不断扩张 $[1,r]$。

算法过程

  • 输入并处理出 $to$ 数组。
  • 初始化指针 $r=to_1$。
  • 遍历区间 $[1,r]$,求出 $mr=\max_{l=1}^{r+1}\{to_l\}$,$mr$ 即为当前能覆盖到的最远的右端点。
    • 若 $mr=r$,意味着无法扩张当前覆盖的区间,即无解,输出 -1,结束。
    • 否则使 $r\gets mr$,答案 $ans\gets ans+1$,回到上一步遍历区间。
  • 若 $r\ge t$,则已经覆盖了整个 $[1,t]$ 区间,故输出答案 $ans$。

考虑到每次都遍历一遍 $[1,r]$ 求 $mr$ 时间复杂度无法承受,可以先预处理前缀右端点最大值数组 $pre$。具体见代码。

代码

#include <iostream>
using namespace std;
typedef pair<int,int> pii;// 方便存储区间
constexpr int T=1145141;
int n,t;
int to[T];// to[l]:l 所对应的所有右端点的最大值
pii pre[T];// pre[i]:前缀右端点最大值数组,表示 左端点在 [1,i] 范围 的区间中 右端点最大的区间。
int main()
{
scanf("%d %d",&n,&t);
for(int i=1,l,r;i<=n;i++)// 读入、预处理 to
{
scanf("%d %d",&l,&r);// 读入每个区间
to[l]=max(to[l],r);// 对于 l 求出最大的 r
}
for(int i=1;i<=t;i++)// 预处理 pre
{
// 如果左端点为 i 的区间右端点比之前大,保存。
if(pre[i-1].second<to[i])pre[i]={i,to[i]};
else pre[i]=pre[i-1];// 否则沿用之前的区间
}
int ans=1,r=to[l];// 由于已经取了区间 [1,to[1]],所以 ans 初始为 1
while(r<t)// 如果还没有覆盖所有区间
{
if(pre[r+1].second==r)// 如果无法扩张 r
{
puts("-1");// 无解
return 0;
}
r=pre[r+1].second;// 移动右端点,扩张
ans++;// 统计答案
}
printf("%d",ans);
return 0;
}
posted @   Po7ed  阅读(68)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示