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$,回到上一步遍历区间。
- 若 $mr=r$,意味着无法扩张当前覆盖的区间,即无解,输出
- 若 $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; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具