题解 [THUPC2017]天天爱射击
题目链接 (洛谷)
题意简述
在一维空间内给定 \(n\) 块木板(区间)和 \(m\) 颗子弹(点)的位置,并给出每块木板需要被多少颗子弹打穿才会破碎。
每颗子弹能贯穿所有挡住了其位置的木板,问每颗子弹能打碎几颗木板。\(( n, m \le 200000 )\)
分析
一眼开出数点问题
维度:一维空间 + 自然的时间维
将时间维离线下来后,本题可抽象描述成:
给定 \(m\) 个二维点 \((x_i, y_i)\) 和 \(n\) 个区间 \([l_i, r_i]\), 求每个二维点满足几个区间使得 \(l_i \le x_i \le r_i\) 且 \(y_i\) 为所有在该区间内的点中第 \(k_i\) 小的 \(y\)(\(k_i\) 就是该木板的耐久)
\(x\) 为位置,\(y_i\) 表示第 \(i\) 颗子弹是第 \(y_i\) 个输入的(也就是说 \(i\) 是第 \(y_i\) 颗打出去的子弹)
题目虽然问的是每个子弹能打碎多少个木板,但很显然这个问题是长在木板上的,因为 \(k_i\) 是木板的属性,所以我们应该考虑求出每个木板是被谁打碎的
(当然,只要稍微学过一点点 DS 的看完上面的问题转换应该都能发现这就是个裸的区间 k 小
到这里问题就解决了
解法
update:笔者写这篇博客的时候水平太菜了,整体二分应该能做到 \(O(n \log n)\)
不带修区间k小可以做到 \(O(n \log n)\) ( 好像还有 \(O(\frac{n \log n}{\log \log n})\) 的做法 ) ,经 (wo) 典 (zhi) 解 (hui) 法 (de) 是用主席树建立前缀线段树,查询时在两个前缀线段树上同时二分
由于本题数据范围较小,这里再推荐两种更好写的解法
1.整体二分 \(O(n \log^2 n)\)
2.莫队 + 简单的根号平衡 \(O(n\sqrt{n})\)
代码 (整体二分)
#include<bits/stdc++.h>
#define lowbit(x) x&-x
using namespace std;
const int N = 200010, inf = 1 << 29;
int n, m, cnt, boa_l[N], boa_r[N], boa_s[N], ans[N], tree[N];
struct bullet{
int pos, num;
bool operator < (const bullet a)const{
return pos < a.pos;
}
}bul[N];
struct question{
int l, r, k, opt;
/*
opt = 1为插入操作(将原数组理解为插入)在l处插入一个值为r的点
opt = 2为询问
*/
}q[4 * N], q1[4 * N], q2[4 * N];
void add(int pos, int val) { for(; pos < N; pos += lowbit(pos)) tree[pos] += val; }
int que(int pos)
{
int res = 0;
for(; pos; pos -= lowbit(pos)) res += tree[pos];
return res;
}
void solve(int l, int r, int L, int R)
{
if(l > r || L > R) return ;
if(l == r){
for(int i = L; i <= R; i++) if(q[i].opt == 2) ans[l]++;
return ;
}
int mid = (l + r) >> 1, cnt1 = 0, cnt2 = 0;
for(int i = L; i <= R; i++){
if(q[i].opt == 1){
if(q[i].r <= mid){
add(q[i].l, 1);
q1[++cnt1] = q[i];
}
else q2[++cnt2] = q[i];
}
else{
int res = que(q[i].r) - que(q[i].l - 1);
if(res >= q[i].k) q1[++cnt1] = q[i];
else q[i].k -= res, q2[++cnt2] = q[i];
}
}
for(int i = 1; i <= cnt1; i++) if(q1[i].opt == 1) add(q1[i].l, -1);
for(int i = 1; i <= cnt1; i++) q[L + i - 1] = q1[i];
for(int i = 1; i <= cnt2; i++) q[L + cnt1 + i - 1] = q2[i];
solve(l, mid, L, L + cnt1 - 1);
solve(mid + 1, r, L + cnt1, R);
}
inline int read()
{
int x = 0, f = 1;
char c = getchar();
while(c < '0' || c > '9') { if(c == '-') f = -1; c = getchar(); }
while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
int main()
{
n = read(), m = read();
for(int i = 1; i <= n; i++) boa_l[i] = read(), boa_r[i] = read(), boa_s[i] = read();
for(int i = 1; i <= m; i++) bul[i].pos = read(), bul[i].num = i;
bul[m + 1] = (bullet){inf, m + 1};
sort(bul + 1, bul + m + 1);
for(int i = 1; i <= m; i++) q[++cnt] = (question){i, bul[i].num, 0, 1};
for(int i = 1; i <= n; i++){
int ql = lower_bound(bul + 1, bul + m + 2, (bullet){boa_l[i], 0}) - bul,
qr = upper_bound(bul + 1, bul + m + 2, (bullet){boa_r[i], 0}) - bul - 1;
//处理并生成新的区间
if(qr - ql + 1 >= boa_s[i]) q[++cnt] = (question){ql, qr, boa_s[i], 2};
}
solve(1, N, 1, cnt);
for(int i = 1; i <= m; i++) printf("%d\n", ans[i]);
return 0;
}
简单的总结
1.数点问题的通用解法就是数维度,在思考的时候不妨将全部维度都离线下来思考
2.k 大,k 小这类问题变换成时间后会非常的诡异,如本题的"木板耐久为 k"
(如果这题空间也是二维的话我可能还真就想不出来了
3.如果输出数字的总和加起来并不大(如本题的 \(\le n\)),说不定这题每个输出数字的每个 1 都要单独计算

浙公网安备 33010602011771号