原题链接
2300分
大意
俄罗斯套娃,每个有内容半径in和外围半径out
in_i<out_i
如果 in_i >= out_j
,那么j可以放在i内
定义残留空间 = 一列嵌套的套娃 未使用的半径和 ,如 {1,2},{2,5},{7,9}
,未使用的白净和为(1-0)+(2-2)+(7-5) = 3
有效残留空间
,如果 一列嵌套的套娃,还能从给出的套娃中选择一个加入这一列,那么原本的残留空间为无效值。如给{1,2},{2,3},{2,5},{7,9}
,只选择{1,2},{7,9}
是无效的嵌套列,而选择{1,2},{2,3},{7,9}
是有效的
令M = 最小的有效残留空间
求有效残留空间
=M的方案数 % MOD
数据范围
套娃个数<= 200'000
半径<=1'000'000'000
MOD = 1'000'000'007
题解 翻译自官方题解
赛内我写了大概十多分钟没写出来,算法是想到n方的 但是没想到怎么优化到时间复杂度内
https://codeforces.com/blog/entry/68615
首先我们 把它们按照内半径排序in_i
,
dp[i] = {x,y}
从末尾到第i个,最小的有效残留空间x, 这样的情况下的方案数 y
dp[i] = {in_i,1}
如果没有 能套在i外面的
如果有能套在i外面的j 那么
dp[i].x = min(d[j].x - (out_i-in_i)) = min(d[j].x) - (out_i-in_i)
我自己考虑漏的一点,假设 我们在尝试i了,那么min[d[ all j such in_j>=out_i ].x]
对于>=i 来说,一定是有效的,当时考虑漏了,在想怎么维护左右游标,来计算有效范围内的最小值
反证法
因为我们 要把i放在j内,如果不是有效的,
首先我们的dp定义保证了 不会插入其它在j以后
那么意味着,在i和j之间还能插入其它,假设为k
那么有d[k].x < d[j].x
和我们刚刚假设的min矛盾
综上
读入O(n)
排序O(n log n)
维护一个minx
数组
每次计算dp[i]
二分>=out_i
的坐标,计算完后更新minx 每次O(log n)
总共O(n log n)
代码 (Rust)
https://codeforces.com/contest/1197/submission/57928269
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| #![allow(unused_imports)] use std::cmp::*; use std::collections::*; use std::ops::Bound::*; struct Scanner { buffer : std::collections::VecDeque<String> } impl Scanner { fn new() -> Scanner { Scanner { buffer: std::collections::VecDeque::new() } } fn next<T : std::str::FromStr >(&mut self) -> T { if self.buffer.len() == 0 { let mut input = String::new(); std::io::stdin().read_line(&mut input).ok(); for word in input.split_whitespace() { self.buffer.push_back(word.to_string()) } } let front = self.buffer.pop_front().unwrap(); front.parse::<T>().ok().unwrap() } } fn main() { const MOD:i32 = 1_000_000_007; let mut s = Scanner::new(); let n : usize = s.next(); let mut arr:Vec<(i32,i32)> = Vec::new(); for _i in 0..n { let out_i: i32 = s.next(); let in_i: i32 = s.next(); arr.push((in_i,out_i)); } arr.sort(); let mut minx:BTreeMap<i32,(i32,i32)> = BTreeMap::new(); let mut ans:i32 = 0; for i in arr.iter().rev() { let out_i = i.1; let in_i = i.0; let bin_res:(i32,i32) = match minx.range((Included(&out_i), Unbounded)).next() { Some(val) => *(val.1), None => (out_i,1) }; let cost = (bin_res.0 - (out_i - in_i), bin_res.1); let cur = match minx.range((Included(&in_i), Unbounded)).next() { Some(val) => *(val.1), None => (cost.0,0) }; let count = minx.entry(in_i).or_insert(cur); if count.0 > cost.0 { ans = cost.1; (*count) = cost; } else if count.0 == cost.0 { (*count).1 += cost.1; (*count).1 %= MOD; ans = (*count).1; } } println!("{}",ans ); }
|
总结
最近在看Rust,想说 这lower_bound
用range来写,写得我自闭,但是回过头来看
感觉用match比c++写 成lower_bound
再判断是否等于.end()
更加清晰
再比如 下面
entry+or_insert
这里
最开始我的两个if里分别时minx.insert
和count操作,这样 连编译都通不过,因为一个变量不允许同时 两个mutable
此外 赞美一下 .0
,.1
相对于 c++中的 .first
,.second
或者说 get<0>(...)
,get<1>(...)
简洁而不失去意义