线段树
题目:
单值修改,区间查询,维护信息:最大值和最大值的个数
题目描述 牛牛有n个宝石,第i个宝石的价值是w[i]. 有m个操作,操作分为两种类型 − Change x y 把第x个宝石的价值改成 y − Ask l r 询问区间[l,r]内宝石的最大价值,和最大价值的宝石有多少个。 输入描述: 第一行两个整数 n , m (1 ≤ n,m ≤ 2e5) 第二行有n个整数 w[i] (0 ≤ w[i] ≤ 1e9) 接下来m行,每行代表一个操作。具体见题目描述。 输出描述: 每次询问输出一行,每行两个整数 val cnt,val代表所有宝石中的最大价值,cnt代表价值最大的宝石有多少个。 示例1 输入 复制 5 3 2 4 3 6 8 Ask 1 5 Change 2 10 Ask 1 3 输出 复制 8 1 10 1
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 200005;
int n, m;
int w[N];
struct Node{
int l, r;
int mx, cnt;
}tr[N * 4];
struct Seg_Tree{
void pushup(Node &c, Node &a, Node &b)
{
if (a.mx == b.mx) c.cnt = a.cnt + b.cnt;
else if (a.mx > b.mx) c.cnt = a.cnt;
else c.cnt = b.cnt;
c.mx = max(a.mx, b.mx);
}
void pushup(int u)
{
pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
}
void build(int u, int l, int r)
{
if (l == r)
{
tr[u] = {l, r, w[l], 1};
return;
}
tr[u] = {l, r};
int mid = l + r >> 1;
build(u << 1, l, mid); build(u << 1 | 1, mid + 1, r);
pushup(u);
}
void change(int u, int x, int c)
{
if (tr[u].l == x && tr[u].r == x)
{
tr[u] = {x, x, c, 1};
return;
}
int mid = tr[u].l + tr[u].r >> 1;
if (x <= mid) change(u << 1, x, c);
if (x > mid) change(u << 1 | 1, x, c);
pushup(u);
}
Node query(int u, int l, int r)
{
if (tr[u].l >= l && tr[u].r <= r) return tr[u];
int mid = tr[u].l + tr[u].r >> 1;
if (r <= mid) return query(u << 1, l, r);
else if (l > mid) return query(u << 1 | 1, l, r);
else
{
Node left = query(u << 1, l, r);
Node right = query(u << 1 | 1, l, r);
Node ans;
pushup(ans, left, right);
return ans;
}
}
}t;
int main(void)
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
t.build(1, 1, n);
char op[10];
int l, r;
while (m -- )
{
scanf("%s", op);
scanf("%d%d", &l, &r);
if (*op == 'C')
{
t.change(1, l, r);
}
else
{
Node x = t.query(1, l, r);
printf("%d %d
", x.mx, x.cnt);
}
}
return 0;
}
扫描线:
题目:
题目描述
求 n 个矩形的面积并。
输入格式
第一行一个正整数 n。
接下来 n 行每行四个非负整数x1,y1,x2,y2,表示一个矩形的左下角坐标为 (x1,y1),右上角坐标为 (x2,y2)。
输出格式
一行一个正整数,表示 n 个矩形的并集覆盖的总面积。
输入输出样例
输入 #12 100 100 200 200 150 150 250 255输出 #118000说明/提示
对于 20% 的数据,1≤n≤1000。
对于 100% 的数据,11≤n≤105,1<x2≤109,0≤y1<y2≤109。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
int n;
struct Segment{
int x, y1, y2, mark;
bool operator< (const Segment &W) const{
return x < W.x;
}
}seg[N << 1];
struct Node{
int l, r;
int tag, len;
}tr[N << 3];
vector<int> ys;
void build(int u, int l, int r)
{
tr[u] = {l, r, 0, 0};
if (l == r) return;
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
}
void pushup(int u)
{
if (tr[u].tag)
{
tr[u].len = ys[tr[u].r + 1] - ys[tr[u].l];
}
else
{
if (tr[u].l == tr[u].r) tr[u].len = 0;
else tr[u].len = tr[u << 1].len + tr[u << 1 | 1].len;
}
}
void update(int u, int l, int r, int c)
{
if (tr[u].l >= l && tr[u].r <= r)
{
tr[u].tag += c;
pushup(u);
return;
}
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) update(u << 1, l, r, c);
if (r > mid) update(u << 1 | 1, l, r, c);
pushup(u);
}
int find(int y)
{
return lower_bound(ys.begin(), ys.end(), y) - ys.begin();
}
int main()
{
cin >> n;
for (int i = 0, j = 0; i < n; i ++ )
{
int x1, y1, x2, y2; scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
seg[j ++ ] = {x1, y1, y2, 1}, seg[j ++ ] = {x2, y1, y2, -1};
ys.push_back(y1), ys.push_back(y2);
}
sort(ys.begin(), ys.end());
ys.erase(unique(ys.begin(), ys.end()), ys.end());
build(1, 0, ys.size() - 2);
sort(seg, seg + 2 * n);
long long ans = 0;
for (int i = 0; i < 2 * n; i ++ )
{
if (i) ans += 1ll * tr[1].len * (seg[i].x - seg[i - 1].x);
update(1, find(seg[i].y1), find(seg[i].y2) - 1, seg[i].mark);
}
printf("%lld
", ans);
return 0;
}
题目:
平面上有n个矩形,给定d,需要找到一个位置(x,y)使得所有的(x + kd, y + kd)均不落在矩形中
分析:
扫描线
我们在使用扫描线的时候,以线段树维护y轴为标准,x是使用点坐标来计算宽的,而y是使用线段也就是区间来计算的。
所以,我们这里对x2-1,y2-1,但是在建立segment的时候,x2还要再加回来,但是,y2 不用加回来,y2-1正好就是线段的长度
而我们的整个正方形区域,按坐标来看,是从[0, 0]到[d - 1,d - 1]的,但是,我们的横坐标,是0~d - 1,但是纵坐标是用线段树维护的,长度是0到d-2
对于x2也要和y2一样都要先-1的另一个原因是,将x2从1~n映射到0~n-1,可以在取模的时候更精准。比如,x1 = 1, x2 = d, 在这种情况下,取模,就得到了x1 = 1, x2 = 0,很显然是不符合我们需要的,这种情况,我们要保证x1小于x2 的,所以,我们将值往前移动一格就完美的解决了这个问题了。而这个技巧也是很常见的。
比如将1~n的数字,映射到二维表格中,我们将原本的1~n先映射为0~n-1,再对每个数取模和下取整的时候,就是很符合规范的映射了,这里就不在赘述了。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
int n, d, ql, qr, mark;
struct Node{
int tag, len;
}tr[N << 2];
struct Segment{
int y1, y2, mark;
};
vector<Segment> vt[N];
void pushup(int l, int r, int rt)
{
if (tr[rt].tag) tr[rt].len = r - l + 1;
else if (l == r) tr[rt].len = 0;
else tr[rt].len = tr[rt << 1].len + tr[rt << 1 | 1].len;
}
void add(int x1, int x2, int y1, int y2)
{
vt[x1].push_back((Segment){y1, y2, 1});
vt[x2 + 1].push_back((Segment){y1, y2, -1});
}
void update(int l, int r, int rt)
{
if (ql <= l && r <= qr)
{
tr[rt].tag += mark;
pushup(l, r, rt);
return;
}
int mid = l + r >> 1;
if (ql <= mid) update(l, mid, 2 * rt);
if (qr > mid) update(mid + 1, r, 2 * rt + 1);
pushup(l, r, rt);
}
void query(int l, int r, int rt)
{
if(tr[rt].len == 0)
{
printf("%d
", l);
return;
}
int mid = l + r >> 1;
if (tr[2 * rt].len < mid - l + 1) query(l, mid, 2 * rt);
else query(mid + 1, r, 2 * rt + 1);
}
int md(int x)
{
return (x % d + d) % d;
}
int main()
{
cin >> n >> d;
for (int i = 1; i <= n; i ++ )
{
int x1, y1, x2, y2; scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
x2 --, y2 --;
if (x2 - x1 + 1 >= d) x1 = 0, x2 = d - 1;
if (y2 - y1 + 1 >= d) y1 = 0, y2 = d - 1;
x1 = md(x1);
y1 = md(y1);
x2 = md(x2);
y2 = md(y2);
if (x1 <= x2)
{
if (y1 <= y2) add(x1, x2, y1, y2);
else add(x1, x2, 0, y2), add(x1, x2, y1, d - 1);
}
else
{
if (y1 <= y2) add(0, x2, y1, y2), add(x1, d - 1, y1, y2);
else add(0, x2, 0, y2), add(0, x2, y1, d - 1), add(x1, d - 1, 0, y2), add(x1, d - 1, y1, d- 1);
}
}
for (int i = 0; i < d; i ++ )
{
for (int j = 0; j < vt[i].size(); j ++ )
{
ql = vt[i][j].y1;
qr = vt[i][j].y2;
mark = vt[i][j].mark;
update(0, d - 1, 1);
}
if (tr[1].len < d)
{
puts("YES");
printf("%d ", i);
query(0, d - 1, 1);
return 0;
}
}
puts("NO");
return 0;
}
下面是逆十字的做法,我在懒标记那里还是有些不懂为什么要这么做的
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 100005;
int n, d;
vector<PII> op0[N], op1[N];
int md(int x)
{
return (x % d + d) % d;
}
//tg为懒标记,
int t[N * 4], tg[N * 4];//t表示当前区间是否有空白,由子节点更新而来,tg表示以u为根节点所再区间是不是全1
//u根节点,l和r表示节点u的左右端点,x和y表示修改区间,c表示求改为c
void update(int u, int l, int r, int x, int y, int c)
{
//当前以u为跟节点的整个区间,都是有c的
if (l >= x && r <= y)
{
t[u] += c, tg[u] += c;
return;
}
int mid = l + r >> 1;
if (x <= mid) update(u << 1, l, mid, x, y, c);
if (y > mid) update(u << 1 | 1, mid + 1, r, x, y, c);
t[u] = min(t[u << 1], t[u << 1 | 1]) + tg[u];
//t[u] == 0表示左子树或者有子树有空白部分,而且u所在区间没有被标记
}
//找到空白部分
int query(int u, int l, int r)
{
if (l == r) return l;
int mid = l + r >> 1;
if (tg[u] + t[u << 1] == t[u]) return query(u << 1, l, mid);
return query(u << 1 | 1, mid + 1, r);
}
int main()
{
scanf("%d%d", &n, &d);
for (int i = 1; i <= n; i ++ )
{
int x1, y1, x2, y2; scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
vector<PII> range1, range2;
x2 --, y2 --;
if (x2 - x1 + 1 >= d) x1 = 0, x2 = d - 1;
if (y2 - y1 + 1 >= d) y1 = 0, y2 = d - 1;
x1 = md(x1), y1 = md(y1), x2 = md(x2), y2 = md(y2);
if (x2 - x1 + 1 >= d) range1.push_back({0, d - 1});
else if (x2 >= x1) range1.push_back({x1, x2});
else {
range1.push_back({x1, d - 1});
range1.push_back({0, x2});
}
if (y2 - y1 + 1 >= d) range2.push_back({0, d - 1});
else if (y2 >= y1) range2.push_back({y1, y2});
else {
range2.push_back({y1, d - 1});
range2.push_back({0, y2});
}
for (auto j : range1)
for (auto k : range2)
{
op0[j.first].push_back(k);
op1[j.second + 1].push_back(k);
}
}
for (int i = 0; i < d; i ++ )
{
//将x=i这一列的所有信息都更新,然后判断这一行是否有空白
for (auto j : op0[i])
update(1, 0, d - 1, j.first, j.second, 1);
for (auto j : op1[i])
update(1, 0, d - 1, j.first, j.second, -1);
if (t[1] == 0)
{
puts("YES");
printf("%d %d
", i, query(1, 0, d - 1));
return 0;
}
}
puts("NO");
return 0;
}