题意 : 给定一个区间长度 n ,接下来给出 m 个子区间,要求最少选出多少个区间才能使得 1~n 这个区间被所选的所有子区间覆盖
分析 :
暴力枚举所有可能的组合可以达到 O( m^m ) ,完全不行
这里考虑动态规划解法
定义 dp[i][j] : 到第 i 个为止,完全覆盖点(即从 1~这个点都能保证被覆盖)到达第 j 个位置所需的最少子区间
则初始化为 dp[0][2~n] = INF、dp[0][1] = 0
假设当前第 i 个子区间用 ( si,ti ) 表示
则状态转移方程为
(ti ≠ j) ==> dp[i+1][j] = dp[i][j]
(ti = j) ==> dp[i+1][j] = min( dp[i][j],min( dp[i][j‘] )+1 ) ( si ≤ j‘ ≤ ti )
可以看出如果当前计算的是第 i 个则 dp 方程只和 i-1 有关系,所以可以用一维数组来优化空间
dp[ti] = min( dp[ti],min( dp[j‘] ) + 1 ) ( si ≤ j‘ ≤ ti )
而如果是要求找出一个区间的最小值的话,我们可以使用线段树来维护,让复杂度降到 logn 级别
#include <cstdio> #include <algorithm> #include <string.h> using namespace std; #define lson l , m , rt << 1 #define rson m + 1 , r , rt << 1 | 1 const int maxn = 5e4 + 10;//maxn = 线段的最大长度 则=> maxn<<2 = 线段树可能的最多结点 const int INF = 0x3f3f3f3f; int minv[maxn<<2];//保存最小值 int dp[maxn]; int L[500005], R[500005]; void PushUP(int rt) { minv[rt] = min(minv[rt<<1], minv[rt<<1|1]); } void build(int l,int r,int rt) {//设置初始值 if (l == r) { minv[rt] = INF; return ; } int m = l + ((r - l)>>1); build(lson); build(rson); PushUP(rt); } void update(int p,int sc,int l,int r,int rt) {//单点更新,参数(更新点,更新值,总区间左端点,总区间右端点,根节点编号) if (l == r) { minv[rt] = sc; return ; } int m = l + ((r - l)>>1); if (p <= m) update(p , sc , lson); else update(p , sc , rson); PushUP(rt); } int query(int L,int R,int l,int r,int rt) {//查询最大值的写法、最小值同理、求和区间写法在下面 if (L <= l && r <= R) return minv[rt]; int m = (l + r) >> 1; int ret = INF; if (L <= m) ret = min(ret , query(L , R , lson)); if (R > m) ret = min(ret , query(L , R , rson)); return ret; } int main(void) { int len, n; while(~scanf("%d %d", &len, &n)){ for(int i=1; i<=n; i++) scanf("%d %d", &L[i], &R[i]); build(1, len, 1); for(int i=1; i<=len; i++) dp[i] = INF; dp[1] = 0; update(1, 0, 1, len, 1); for(int i=1; i<=n; i++){ int val = query(L[i], R[i], 1, len, 1) + 1; if(val < dp[R[i]]){ //printf("%d %d\n", L[i], R[i]); update(R[i], val, 1, len, 1); dp[R[i]] = val; } } printf("%d\n", dp[len]); } return 0; }