第十八届浙江省赛 B Restore Atlantis 线段树+扫描线+树状数组
题目大意:
给一个整数n和q,代表会输入n个矩形和q个询问
然后输入四个数字,前两个数字代表矩形的左下角(按照高中那种坐标系),后两个数字代表矩形的右上角坐标,按照输入的顺序依次给每个矩形编号为1 - n(下文统称为id)
紧接着读入q组询问,每组询问给我们一个区间[si, ti],代表id在si到ti的矩形当做没出现过,即si <= id <= ti不纳入面积计算。对于每组询问我们都需要算总面积。
做法1:
不得不先说一下做法二是在线的主席树做法,时间和空间不够用,正确性也未知,虽然本人感觉很对就是了。
我们可以发现矩形的边界最多为2e3,那么也就是说我们完全可以维护所有的1×1的小格子,最多也就是4e6个小格子,这个大小我们完全可以接受。找到每个被矩形覆盖过的格子的最小编号和最大编号,那么对于每组询问问题就变成了有多少个被覆盖的格子最小编号小于si或者最大编号大于ti。
1. 那么我们该如何计算被覆盖的格子的最小编号和最大编号呢?我们使用线段树+扫描线(不会的话还是先去学一下)。每次扫描一个区间的时候把编号也丢进去。对于线段树的每个区间我们都使用两个优先队列(set会t),一个是小根堆另一个是大根堆,维护被完全覆盖的区间的所有编号。扫描线扫到左端点的时候就可以就行区间修改,丢进去当前的线段[y1, y2]还有id。(修改最大最小值的时候线段树区间被完全覆盖了就不用再往下递归了,这里的优先队列也能起到懒标记的作用)。扫到了右端点就对当前右端点对应的编号进行标记(统计最大最小编号的时候需要pop掉这些编号,代码里面用的vis数组进行标记)。每次扫完一个大小为1 * 2000的区间(认为是x轴走了一个格子,线段树维护的是y轴)我们都需要统计一下当前的1 * 2000的的小矩形里面所有被覆盖的点的最大编号和最小编号并将其丢进一个二元组(这里我使用的pair,并命名pos)里面,这样的话我们就得到了所有被矩形覆盖了的1*1小格子的最小编号和最大编号。
2. 那么如何统计每组询问的答案呢?我们发现这题没有修改操作并且没有强制在线,那么我们就可以考虑离线+树状数组统计答案。我们在存二元组的时候顺便用一个变量tot计算被覆盖的所有1 * 1的小格子的数量,这样一来我们只需要统计每组询问有多少个小格子被完全覆盖了就行。我们将储存每个格子最小最大编号的二元组(我命名为pos)按照格子编号最大值升序排序。然后读入所有询问l,r并丢进一个三元组里面(因为还要存是第几个询问的答案,将这个三元组命名为q),我们将q按照r升序排序。树状数组的每个坐标表示询问的r,我们在从左往右遍历每个1*2000的矩阵的时候,一但pos的first小于等于当前的r了我们就将pos的second添加到树状数组里面,然后统计被l,r完全覆盖的小格子的数量,用tot-这个数量就是当前的答案。
#include <iostream>
#include <cstring>
#include <iomanip>
#include <algorithm>
#include <stack>
#include <queue>
#include <numeric>
#include <cassert>
#include <bitset>
#include <cstdio>
#include <vector>
#include <unordered_set>
#include <cmath>
#include <map>
#include <unordered_map>
#include <set>
#include <deque>
#include <tuple>
#define all(a) a.begin(), a.end()
#define cnt0(x) __builtin_ctz(x)
#define endl '\n'
#define itn int
#define ll long long
#define ull unsigned long long
#define rep(i, a, b) for(int i = a;i <= b; i ++)
#define per(i, a, b) for(int i = a;i >= b; i --)
#define cntone(x) __builtin_popcount(x)
#define db double
#define fs first
#define se second
#define AC main(void)
#define HYS std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);
typedef std::pair<int, int > PII;
typedef std::pair<int, std::pair<int, int>> PIII;
typedef std::pair<ll, ll> Pll;
typedef std::pair<double, double> PDD;
using ld = double long;
const long double eps = 1e-9;
const int INF = 0x3f3f3f3f;
const int N = 2e3 + 10, M = 1e5 + 10;
int n , m, _ = 1;
bool vis[M];
std::priority_queue<int> maxm[N << 2];
std::priority_queue<int, std::vector<int>, std::greater<int>> maxn[N << 2];
int tot;
struct node{
int x, y1, y2, flag, id;
friend bool operator < (const node &t1, const node t2) {return t1.x < t2.x;}
}seg[M << 1];
std::vector<PII> pos;
inline void modify(int u, int L, int R, int l, int r, int id){
if(L >= l && R <= r){
maxm[u].push(id);
maxn[u].push(id);
return ;
}
int mid = L + R >> 1;
if(l <= mid) modify(u << 1, L, mid, l, r, id);
if(r > mid) modify(u << 1 | 1, mid + 1, R, l, r, id);
}
inline void add(int u, int L, int R, int mx, int mn){
while(!maxm[u].empty() && vis[maxm[u].top()]) maxm[u].pop();
while(!maxn[u].empty() && vis[maxn[u].top()]) maxn[u].pop();
if(!maxm[u].empty()) mx = std::max(mx, maxm[u].top()), mn = std::min(mn, maxn[u].top());
if(L == R){
if(mx) pos.push_back({mx, mn}), tot ++;
return ;
}
int mid = L + R >> 1;
add(u << 1, L, mid, mx, mn);
add(u << 1 | 1, mid + 1, R, mx, mn);
}
struct Fenwick{
int maxm, cnt = 0;
std::vector<int> tr;
Fenwick(int n): tr(n + 1, 0) {maxm = n;}
inline int lowbit(int x) {return x & -x;}
inline void add(int x, int v){
for(int i = x; i <= maxm; i += lowbit(i)) tr[i] += v;
cnt += v;
}
inline int query(int x){
int res = 0;
for(int i = x; i > 0; i -= lowbit(i)) res += tr[i];
return res;
}
inline int query(int l, int r){
return query(r) - query(l - 1);
}
};
int ans[M];
void solve(){
std::cin >> n >> m;
std::vector<int> nums;
for(int i = 1, j = 0; i <= n; i ++){
int x1, x2, y1, y2;
std::cin >> x1 >> y1 >> x2 >> y2;
y1 ++, y2 ++;
seg[j ++] = {x1, y1, y2, 1, i};
seg[j ++] = {x2, y1, y2, -1, i};
}
std::sort(seg, seg + 2 * n);
for(int i = 0, j = 0; i <= 2000; i ++){
while(seg[j].x == i){
if(seg[j].flag == 1){
modify(1, 1, 2000, seg[j].y1, seg[j].y2 - 1, seg[j].id);
}else vis[seg[j].id] = true;
j ++;
}
add(1, 1, 2000, 0, n);
}
Fenwick fk(n);
std::vector<std::tuple<int, int, int>> q(m);
for(int i = 0; i < m; i ++){
int l, r;
std::cin >> l >> r;
q[i] = {r, l, i + 1};
}
std::sort(pos.begin(), pos.end());
std::sort(q.begin(), q.end());
int cnt = 0;
for(int i = 0, j = 0; i < m; i ++){
auto &[y, x, id] = q[i];
while(pos[j].fs <= y && j < tot) fk.add(pos[j ++].se, 1);
ans[id] = tot - fk.query(x, y);
}
for(int i = 1; i <= m; i ++) std::cout << ans[i] << '\n';
}
signed AC{
HYS
_ = 1;
//std::cin >> _;
while(_ --)
solve();
return 0;
}
做法2
是个假写法
在线做法:把离线+树状数组换成主席树即可
想写个在线的做法,就用了主席树,果不其然时间空间都不够用,正确性也未知,但是自己对拍1000以内都是对的
我们知道主席树可以维护每个历史版本,所以我们将所有不同的最大值视为每个版本,还是将pos按照first(最大值)升序排序。因为编号的最大值范围是从1到n。
所以我们遍历i从1到n即可,然后j维护当前遍历到第几个pos,每当pos[j]的first(即当前格子id最大值)等于i,我们就将pos[j]的second加入主席树。
最后查询si,ti的时候我们只需要查询第si-1个版本和第t1个版本中si和ti出现的数量,tot-这个数量既是答案。
#include <iostream>
#include <cstring>
#include <iomanip>
#include <algorithm>
#include <stack>
#include <queue>
#include <numeric>
#include <cassert>
#include <bitset>
#include <cstdio>
#include <vector>
#include <unordered_set>
#include <cmath>
#include <map>
#include <unordered_map>
#include <set>
#include <deque>
#include <tuple>
#define all(a) a.begin(), a.end()
#define cnt0(x) __builtin_ctz(x)
#define endl '\n'
#define itn int
#define ll long long
#define ull unsigned long long
#define rep(i, a, b) for(int i = a;i <= b; i ++)
#define per(i, a, b) for(int i = a;i >= b; i --)
#define cntone(x) __builtin_popcount(x)
#define db double
#define fs first
#define se second
#define AC main(void)
#define HYS std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);
typedef std::pair<int, int > PII;
typedef std::pair<int, std::pair<int, int>> PIII;
typedef std::pair<ll, ll> Pll;
typedef std::pair<double, double> PDD;
using ld = double long;
const long double eps = 1e-9;
const int INF = 0x3f3f3f3f;
const int N = 2e3 + 10, M = 1e5 + 10;
int n , m, _ = 1;
bool vis[M];
std::priority_queue<int> maxm[N << 2];
std::priority_queue<int, std::vector<int>, std::greater<int>> maxn[N << 2];
int tot;
struct node{
int x, y1, y2, flag, id;
friend bool operator < (const node &t1, const node t2) {return t1.x < t2.x;}
}seg[M << 1];
std::vector<PII> pos;
inline void modify(int u, int L, int R, int l, int r, int id){
if(L >= l && R <= r){
maxm[u].push(id);
maxn[u].push(id);
return ;
}
int mid = L + R >> 1;
if(l <= mid) modify(u << 1, L, mid, l, r, id);
if(r > mid) modify(u << 1 | 1, mid + 1, R, l, r, id);
}
inline void add(int u, int L, int R, int mx, int mn){
while(!maxm[u].empty() && vis[maxm[u].top()]) maxm[u].pop();
while(!maxn[u].empty() && vis[maxn[u].top()]) maxn[u].pop();
if(!maxm[u].empty()) mx = std::max(mx, maxm[u].top()), mn = std::min(mn, maxn[u].top());
if(L == R){
if(mx) pos.push_back({mx, mn}), tot ++;
return ;
}
int mid = L + R >> 1;
add(u << 1, L, mid, mx, mn);
add(u << 1 | 1, mid + 1, R, mx, mn);
}
struct Node{
int l, r, cnt;
}tr[M * 390];
struct Historical_Segment_Tree{
int idx;
int root[M];
inline int build(int l, int r){
int q = ++ idx;
if(l == r) return q;
int mid = l + r >> 1;
tr[q].l = build(l, mid);//存的是左儿子的编号并非边界
tr[q].r = build(mid + 1, r);
return q;
}
inline int insert(int p, int l, int r, int x, int sum){//需要用到的上一个版本root[i - 1](返回的值是当前版本的root)
int q = ++ idx;
tr[q] = tr[p];//复制上一个节点的信息
if(l == r){
tr[q].cnt += sum;//新版本的信息加1
return q;
}
int mid = l + r >> 1;
if(x <= mid) tr[q].l = insert(tr[p].l, l, mid, x, sum);//在左子树则需要更新信息,否则保留原本信息就可以
else tr[q].r = insert(tr[p].r, mid + 1, r, x, sum);
tr[q].cnt = tr[tr[q].l].cnt + tr[tr[q].r].cnt;
return q;
}
inline int query(int p, int q, int L, int R, int l, int r){
if(L >= l && R <= r) return tr[q].cnt - tr[p].cnt;
int mid = L + R >> 1;
int cnt = 0;
if(l <= mid) cnt += query(tr[p].l, tr[q].l, L, mid, l, r);
if(r > mid) cnt += query(tr[p].r, tr[q].r, mid + 1, R, l, r);
return cnt;
}
inline int queryl(int p, int L, int R, int l, int r){
if(L >= l && R <= r) return tr[p].cnt;
int mid = L + R >> 1;
int cnt = 0;
if(l <= mid) cnt += queryl(tr[p].l, L, mid, l, r);
if(r > mid) cnt += queryl(tr[p].r, mid + 1, R, l, r);
return cnt;
}
inline int query(int p, int q, int l, int r, int k){//区间k小
if(l == r) return l;
int mid = l + r >> 1;
int cnt = tr[tr[q].l].cnt - tr[tr[p].l].cnt;
if(cnt >= k) return query(tr[p].l, tr[q].l, l, mid, k);
else return query(tr[p].r, tr[q].r, mid + 1, r, k - cnt);
}
}HST;
void solve(){
std::cin >> n >> m;
std::vector<int> nums;
for(int i = 1, j = 0; i <= n; i ++){
int x1, x2, y1, y2;
std::cin >> x1 >> y1 >> x2 >> y2;
x1 ++, y1 ++, x2 ++, y2 ++;
seg[j ++] = {x1, y1, y2, 1, i};
seg[j ++] = {x2, y1, y2, -1, i};
}
std::sort(seg, seg + 2 * n);
for(int i = 1, j = 0; i <= 2000; i ++){
while(seg[j].x == i){
if(seg[j].flag == 1){
modify(1, 1, 2000, seg[j].y1, seg[j].y2 - 1, seg[j].id);
}else vis[seg[j].id] = true;
j ++;
}
add(1, 1, 2000, 0, n);
}
std::sort(pos.begin(), pos.end());
auto &root = HST.root;
root[0] = HST.build(1, n);
for(int i = 1, j = 0; i <= n; i ++){
if(pos[j].fs == i){
root[i] = HST.insert(root[i - 1], 1, n, pos[j].se, 1);
j ++;
while(pos[j].fs == i){
root[i] = HST.insert(root[i], 1, n, pos[j].se, 1);
j ++;
}
}
else root[i] = root[i - 1];
}
for(int i = 1; i <= m; i ++){
int l, r;
std::cin >> l >> r;
std::cout << tot - HST.query(root[l - 1], root[r], 1, n, l, r) << '\n';
}
}
signed AC{
HYS
_ = 1;
//std::cin >> _;
while(_ --)
solve();
return 0;
}