线段树优化 dp && 扫描线
Part1. 线段树优化 dp
CF115E
设 表示前 条路中修复若干条路后可以赚到的最多的钱。
不修第 条路时
区间 中的路都修好时有 。 表示修好区间中所有路所要花的钱; 表示区间中的路都修好的情况下,举行比赛可以赚到的钱。
线段树优化,第 个位置 储存
枚举 ,将 减去修路的价钱,因为都要多修一条路。对于每个右端点 的比赛 ,将 都加上 。在这些状态中, 区间的路都修好可以进行比赛。更新状态 。更新 。
复杂度
int n, m;
int v[N];
int f[N];
struct add
{
int l, k;
add(int p, int q)
{
l = p, k = q;
}
};
vector<add> vec[N];
struct Node
{
int v, tag;//v is max
} t[N * 4];
#define ls p << 1
#define rs p << 1 | 1
void push_up(int p)
{
t[p].v = max(t[ls].v, t[rs].v);
}
void push_down(int p)
{
t[ls].v += t[p].tag, t[ls].tag += t[p].tag;
t[rs].v += t[p].tag, t[rs].tag += t[p].tag;
t[p].tag = 0;
}
void change(int p, int l, int r, int x, int y, int d)
{
if (l >= x && r <= y)
{
t[p].v += d;
t[p].tag += d;
return ;
}
push_down(p);
int mid = (l + r) >> 1;
if (x <= mid) change(ls, l, mid, x, y, d);
if (y > mid) change(rs, mid + 1, r, x, y, d);
push_up(p);
}
int query(int p, int l, int r, int x, int y)
{
if (l >= x && r <= y) return t[p].v;
push_down(p);
int mid = (l + r) >> 1;
int res = 0;
if (x <= mid) res = max(res, query(ls, l, mid, x, y));
if (y > mid) res = max(res, query(rs, mid + 1, r, x, y));
return res;
}
signed main()
{
cin >> n >> m;
for (rint i = 1; i <= n; i++) cin >> v[i];
for (rint i = 1; i <= m; i++)
{
int x, y, z;
cin >> x >> y >> z;
vec[y].push_back(add(x, z));
}
for (rint i = 1; i <= n; i++)
{
change(1, 0, n, 0, i - 1, -v[i]);
for (rint j = 0; j < vec[i].size(); j++)
{
int ll = vec[i][j].l, kk = vec[i][j].k;
change(1, 0, n, 0, ll - 1, kk);
}
f[i] = max(query(1, 0, n, 0, i - 1), f[i - 1]);
change(1, 0, n, i, i, f[i]);
}
cout << f[n] << endl;
return 0;
}
CF474E Pillars
设 表示以 结尾的满足条件的最长子序列
考虑这个限制条件每次转移需要用最大值,用线段树维护来优化。每次在线段树上下标为 处,拆入 。查询操作:我们把绝对值拆开 或 ,等价成询问区间最大值。
方案则在线段树上再维护一个区间最大值的位置,求 时记录前驱即可
struct Node
{
int v, pos;//v is max
Node(){v = 0, pos = 0;};
bool friend operator < (Node x, Node y)
{
return x.v < y.v;
}
} t[M * 4];
#define ls p << 1
#define rs p << 1 | 1
void push_up(int p)
{
t[p] = max(t[ls], t[rs]);
}
void change(int p, int l, int r, int x, int v, int d)
{
if (l == r)
{
if (v > t[p].v) t[p].v = v, t[p].pos = d;
return ;
}
int mid = (l + r) >> 1;
if (x <= mid) change(ls, l, mid, x, v, d);
else change(rs, mid + 1, r, x, v, d);
push_up(p);
}
Node query(int p, int l, int r, int x, int y)
{
if (l >= x && r <= y) return t[p];
int mid = (l + r) >> 1;
Node res;
if (x <= mid) res = max(res, query(ls, l, mid, x, y));
if (y > mid) res = max(res, query(rs, mid + 1, r, x, y));
return res;
}
void print(int x)
{
while (x)
{
ans[++cnt] = x;
x = pre[x];
}
for (rint i = cnt; i >= 1; i--)
{
cout << ans[i] << " ";
}
}
signed main()
{
cin >> n >> d;
for (rint i = 1; i <= n; i++)
{
cin >> b[i];
a[i] = b[i];
b[i + n] = a[i + n] = a[i] - d;
b[i + 2 * n] = a[i + 2 * n] = a[i] + d;
}
sort(b + 1, b + 3 * n + 1);
int tot = unique(b + 1, b + 3 * n + 1) - b - 1;
for (rint i = 1; i <= 3 * n; i++) a[i] = lower_bound(b + 1, b + tot + 1, a[i]) - b;
f[1] = 1;
change(1, 1, tot, a[1], 1, 1);
for (rint i = 2; i <= n; i++)
{
Node x, y, z;
x = query(1, 1, tot, 1, a[i + n]);
y = query(1, 1, tot, a[i + 2 * n], tot);
z = max(x, y);
f[i] = z.v + 1;
pre[i] = z.pos;
change(1, 1, tot, a[i], f[i], i);
}
for (rint i = 1; i <= n; i++)
{
if (maxx < f[i]) maxx = f[i], id = i;
}
cout << maxx << endl;
print(id);
return 0;
}
[POI2005] AUT-The Bus
设 表示从 走到 的
横纵坐标只能递增,显然二位偏序,在以当前点为右下角的矩形中查一个最大值出来转移,只需要选一个坐标为第一关键字排个序即可
每次 query
时, 出现在 范围内的最大值一定已经是之前做过的合法解
int n, m, k;
int ans;
int b[N];
struct node
{
int x, y, v;
friend bool operator < (node a, node b)
{
return (a.y == b.y) ? (a.x < b.x) : (a.y < b.y);
}
}p[M];
struct Node
{
int v;//v is max
Node(){v = 0;};
} t[M * 4];
#define ls p << 1
#define rs p << 1 | 1
void push_up(int p)
{
t[p].v = max(t[ls].v, t[rs].v);
}
void change(int p, int l, int r, int x, int v)
{
if (l == r)
{
if (v > t[p].v) t[p].v = v;
return ;
}
int mid = (l + r) >> 1;
if (x <= mid) change(ls, l, mid, x, v);
else change(rs, mid + 1, r, x, v);
push_up(p);
}
int query(int p, int l, int r, int x, int y)
{
if (l >= x && r <= y) return t[p].v;
int mid = (l + r) >> 1;
int res = 0;
if (x <= mid) res = max(res, query(ls, l, mid, x, y));
if (y > mid) res = max(res, query(rs, mid + 1, r, x, y));
return res;
}
signed main()
{
cin >> n >> m >> k;
for (rint i = 1; i <= k; i++)
{
cin >> p[i].x >> p[i].y >> p[i].v;
b[i] = p[i].x;
}
b[k + 1] = 0;
sort(b + 1, b + 2 + k);
int tot = unique(b + 1, b + 2 + k) - (b + 1);
sort(p + 1, p + 1 + k);
for (rint i = 1; i <= k; i++)
{
p[i].x = lower_bound(b + 1, b + 1 + tot, p[i].x) - b;
int ret = query(1, 1, tot, 1, p[i].x);
ret += p[i].v;
change(1, 1, tot, p[i].x, ret);
}
cout << query(1, 1, tot, 1, tot) << endl;
return 0;
}
CF833B The Bakery
设 表示前 个数分为 段的最大价值
有
设位置为 ,表示的数为 , 记录 的前驱, 那么 的贡献就只有 这些位置上的数有贡献。
用线段树进行区间修改操作和查询最大值的操作
int n, m;
int a[N];
int f[N][M], pre[N], id[N];
bool vis[N];
int p[N];
struct Segment_Tree
{
struct node
{
int l, r;
int v, tag;
} t[4 * N];
#define ls p << 1
#define rs p << 1 | 1
void push_up(int p)
{
t[p].v = max(t[ls].v, t[rs].v);
}
void push_down(int p)
{
t[ls].v += t[p].tag, t[rs].v += t[p].tag;
t[ls].tag += t[p].tag, t[rs].tag += t[p].tag;
t[p].tag = 0;
}
void clear(int i, int p, int l, int r)
{
t[p].l = l;
t[p].r = r;
if (l == r)
{
t[p].v = f[l][i];
return ;
}
int mid = (l + r) >> 1;
clear(i, ls, l, mid);
clear(i, rs, mid + 1, r);
}
void change(int p, int l, int r, int x)
{
if (t[p].l >= l && t[p].r <= r)
{
t[p].tag += x;
t[p].v += x;
return ;
}
push_down(p);
int mid = (t[p].l + t[p].r) >> 1;
if (l <= mid) change(ls, l, r, x);
if (r > mid) change(rs, l, r, x);
push_up(p);
}
int query(int p, int l, int r)
{
if (t[p].l >= l && t[p].r <= r) return t[p].v;
push_down(p);
int mid = (t[p].l + t[p].r) >> 1;
int res = 0;
if (l <= mid) res = query(ls, l, r);
if (r > mid) res = max(res, query(rs, l, r));
return res;
}
} tr[M];
signed main()
{
cin >> n >> m;
for (rint i = 1; i <= n; i++)
{
cin >> a[i];
pre[i] = id[a[i]];
id[a[i]] = i;
}
for (rint i = 1; i <= n; i++)
{
f[i][1] = f[i - 1][1];
if (!vis[a[i]])
{
vis[a[i]] = 1;
f[i][1]++;
}
}
for (rint j = 2; j <= m; j++)
{
tr[j - 1].clear(j - 1, 1, 0, n);
for (rint i = 1; i <= n; i++)
{
tr[j - 1].change(1, pre[i], i - 1, 1);
f[i][j] = tr[j - 1].query(1, 0, i - 1);
}
}
cout << f[n][m] << endl;
return 0;
}
[NOIP2023] 天天爱打卡
有 个区间 ,每个区间有一个权值 ,选一个点需要花费 的代价,但选满一个区间后可以得到 的代价,任意长为 的一段不能全被选,求最大代价。
设 表示前 天的最大答案
,其中 表示 的
如果要恰好包含一些区间,而不浪费多选点的话,可能的转移点只会是所有的 。线段树优化。
int n, m;
int k, d;
int l[N], r[N], w[N], b[M], f[M];
vector<int> v[M];
struct Node
{
int v, tag;
} t[M];
#define ls p << 1
#define rs p << 1 | 1
void push_up(int p)
{
t[p].v = max(t[ls].v, t[rs].v);
}
void push_down(int p)
{
t[ls].tag += t[p].tag, t[rs].tag += t[p].tag;
t[ls].v += t[p].tag, t[rs].v += t[p].tag;
t[p].tag = 0;
}
void build(int p, int l, int r)
{
t[p].tag = 0;
if (l == r)
{
t[p].v = 0;
return;
}
int mid = (l + r) >> 1;
build(ls, l, mid);
build(rs, mid + 1, r);
push_up(p);
}
void change(int p, int l, int r, int x, int y, int d)
{
if (x <= l && r <= y)
{
t[p].v += d, t[p].tag += d;
return;
}
int mid = (l + r) >> 1;
push_down(p);
if (x <= mid) change(ls, l, mid, x, y, d);
if (y > mid) change(rs, mid + 1, r, x, y, d);
push_up(p);
}
int query(int p, int l, int r, int x, int y)
{
if (x > y) return -inf;
if (x <= l && r <= y) return t[p].v;
push_down(p);
int mid = (l + r) >> 1;
int res = -inf;
if (x <= mid) res = max(res, query(ls, l, mid, x, y));
if (y > mid) res = max(res, query(rs, mid + 1, r, x, y));
return res;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int C, T;
cin >> C >> T;
while (T--)
{
int tot = 0;
cin >> n >> m >> k >> d;
for (rint i = 1; i <= m; i++)
{
int x, y;
cin >> x >> y >> w[i];
l[i] = x - y + 1;
r[i] = x;
b[++tot] = l[i] - 1;
b[++tot] = r[i] + 1;
}
sort(b + 1, b + tot + 1);
tot = unique(b + 1, b + tot + 1) - b - 1;
for (rint i = 1; i <= m; i++)
{
l[i] = lower_bound(b + 1, b + tot + 1, l[i] - 1) - b;
r[i] = lower_bound(b + 1, b + tot + 1, r[i] + 1) - b;
v[r[i]].push_back(i);
}
build(1, 1, tot);
f[1] = 0;
change(1, 1, tot, 1, 1, f[1] + b[1] * d);
for (rint i = 2, j = 1; i <= tot; i++)
{
for (auto x : v[i]) change(1, 1, tot, 0, l[x], w[x]);
while (b[i] - b[j] - 1 > k) j++;
f[i] = max(f[i - 1], query(1, 1, tot, j, i - 1) - (b[i] - 1) * d);
change(1, 1, tot, i, i, f[i] + b[i] * d);
}
cout << f[tot] << endl;
for (rint i = 0; i <= tot; i++)
{
f[i] = 0;
v[i].clear();
}
}
return 0;
}
[NOIP2023] 双序列拓展 75pts solution
设 表示 序列的前 个是否能和 序列的前 个匹配,这个 dp 理论值无论怎么写也只有 35pts,但是考虑线段树优化,可以卡到 75pts。
struct Node
{
int maxx;
int minn;
}t[M];
#define ls p << 1
#define rs p << 1 | 1
void push_up(int p)
{
t[p].maxx = max(t[ls].maxx, t[rs].maxx);
t[p].minn = min(t[ls].minn, t[rs].minn);
}
void build(int p, int l, int r, int *a)
{
if (l == r)
{
t[p].maxx = t[p].minn = a[l];
return;
}
int mid = (l + r) >> 1;
build(ls, l, mid, a);
build(rs, mid + 1, r, a);
push_up(p);
}
int query_1(int p, int l, int r, int x, int v)
{
if (l == r) return (t[p].maxx < v ? l : 0);
int mid = (l + r) >> 1;
int res = 0;
if (x > mid && t[rs].minn < v) res = query_1(rs, mid + 1, r, x, v);
//加上一个 t[rs].minn < v 可以加速, 多 20 pts
if (!res) res = query_1(ls, l, mid, x, v);
return res;
}
int query_2(int p, int l, int r, int x, int v)
{
if (l == r) return (t[p].minn >= v ? l : 0);
int mid = (l + r) >> 1;
int res = 0;
if (x < mid && t[ls].maxx >= v)
res = query_2(ls, l, mid, x, v);
if (!res) res = query_2(rs, mid + 1, r, x, v);
return res;
}
bool calc()
{
if (a[1] > b[1] && a[n] > b[m])
{
build(1, 1, m, b);
int minn = t[1].minn;
int j = 1;
bool tag = 0;
for (rint i = 1; i <= n; i++)
if (a[i] <= minn)
tag = 1;
if (!tag)
{
for (rint i = 1; i <= n; i++)
{
bool flag = 1;
if (a[i] <= b[j]) flag = 0;
else
{
int res = query_2(1, 1, m, j, a[i]);
if (!res) res = m;
if (res == j) flag = 0;
j = res;
}
if (!flag && a[i] <= b[j])
{
int res = query_1(1, 1, m, j, a[i]);
if (!res) { tag = 1; break;}
j = res + 1;
}
}
}
if (!tag && j == m) return 1;
}
if (b[1] > a[1] && b[m] > a[n])
{
build(1, 1, n, a);
int minn = t[1].minn;
int j = 1;
bool tag = 0;
for (rint i = 1; i <= m; i++)
if (b[i] <= minn)
tag = 1;
if (!tag)
{
for (rint i = 1; i <= m; i++)
{
bool flag = 1;
if (b[i] <= a[j]) flag = 0;
else
{
int res = query_2(1, 1, n, j, b[i]);
if (!res) res = n;
if (res == j) flag = 0;
j = res;
}
if (!flag && b[i] <= a[j])
{
int res = query_1(1, 1, n, j, b[i]);
if (!res) { tag = 1; break;}
j = res + 1;
}
}
}
if (!tag && j == n) return 1;
}
return 0;
}
signed main()
{
cin >> c >> n >> m >> q;
for (rint i = 1; i <= n; i++) _a[i] = a[i] = read();
for (rint i = 1; i <= m; i++) _b[i] = b[i] = read();
cout << calc();
while (q--)
{
for (rint i = 1; i <= n; i++) a[i] = _a[i];
for (rint i = 1; i <= m; i++) b[i] = _b[i];
int kx = read(), ky = read();
while (kx--) a[read()] = read();
while (ky--) b[read()] = read();
cout << calc();
}
return 0;
}
Part2.扫描线
P5490 【模板】扫描线
实现思路:
拟扫描线从下向上扫过图形,计算出当前扫描线被截得的长度
扫描线每次会在碰到横边的时候停下来
可对图形面积产生影响的元素,这些横边左右端点的坐标
为了快速计算出截线段长度,可以将横边赋上不同的权值,对于一个矩形,其下边权值为 ,上边权值为
然后把所有的横边按照 坐标升序排序。这样,对于每个矩形,扫描线总是会先碰到下边,然后再碰到上边。那么就能保证扫描线所截的长度永远非负了。
这样操作以后,就可以和线段树扯上关系。先把所有端点在 轴上离散化
建立一棵线段树,其每个端点维护一条线段的信息: 被覆盖了多少次被多少个矩形所覆盖, 被整个图形所截的长度是多少
只要一条线段被覆盖,那么它肯定被图形所截。所以,整个问题就转化为了一个区间查询问题,即:每次将 当前扫描线扫到的边 对应的信息 按照之前赋上的权值更新,然后再查询线段树根节点的信息,最后得到当前扫描线扫过的面积。
#include <iostream>
#include <algorithm>
#define rint register int
#define int long long
#define endl '\n'
using namespace std;
const int N = 1e6 + 5;
const int M = N << 1;
int n, cnt = 0;
int x1, y1, x2, y2;
int b[M];
struct ScanLine
{
int l, r, h;
int mark;
// mark用于保存权值 (1 / -1)
friend bool operator < (ScanLine a, ScanLine b)
{
return a.h < b.h;
}
} line[M];
struct Node
{
int l, r;
int sum;
int len;
// sum: 被完全覆盖的次数;
// len: 区间内被截的长度。
} t[N << 2];
#define ls p << 1
#define rs p << 1 | 1
void build(int p, int l, int r)
{
t[p].l = l;
t[p].r = r;
t[p].len = 0;
t[p].sum = 0;
if (l == r) return;
int mid = (l + r) >> 1;
build(ls, l, mid);
build(rs, mid + 1, r);
return ;
}
void push_up(int p)
{
if (t[p].sum) t[p].len = b[t[p].r + 1] - b[t[p].l];
else t[p].len = t[ls].len + t[rs].len;
}
void change(int p, int l, int r, int c)
{
if (b[t[p].r + 1] <= l || r <= b[t[p].l]) return;
if (l <= b[t[p].l] && b[t[p].r + 1] <= r)
{
t[p].sum += c;
push_up(p);
return;
}
change(ls, l, r, c);
change(rs, l, r, c);
push_up(p);
}
signed main()
{
cin >> n;
for (rint i = 1; i <= n; i++)
{
cin >> x1 >> y1 >> x2 >> y2;
b[2 * i - 1] = x1;
b[2 * i] = x2;
line[2 * i - 1] = {x1, x2, y1, 1};
line[2 * i] = {x1, x2, y2, -1};
}
n <<= 1;
sort(line + 1, line + n + 1);
sort(b + 1, b + n + 1);
int tot = unique(b + 1, b + n + 1) - b - 1;
build(1, 1, tot - 1);
int ans = 0;
for (rint i = 1; i < n; i++)
{
change(1, line[i].l, line[i].r, line[i].mark);
ans += t[1].len * (line[i + 1].h - line[i].h);
}
cout << ans << endl;
return 0;
}
P1856 [IOI1998] 矩形周长
观察这三条扫描线扫过的纵边,有 纵边总长度。
横边总长度-
长并中的线段树还要维护线段的条数。另外,横边和纵边需分别计算。
#include <iostream>
#include <algorithm>
#define rint register int
#define int long long
#define endl '\n'
using namespace std;
const int N = 1e4 + 5;
const int M = N << 1;
int n, pre = 0;
int x1, y1, x2, y2;
int b[M];
struct ScanLine
{
int l, r, h;
int mark;
// mark用于保存权值 (1 / -1)
friend bool operator < (ScanLine a, ScanLine b)
{
if (a.h == b.h) return a.mark > b.mark;
return a.h < b.h;
}
} line[M];
struct Node
{
int l, r;
int sum;
int len;
int c;
bool lc, rc;
// sum: 被完全覆盖的次数;
// len: 区间内被截的长度。
// c表示区间线段条数
// lc, rc分别表示左、右端点是否被覆盖
} t[N << 2];
#define ls p << 1
#define rs p << 1 | 1
void build(int p, int l, int r)
{
t[p].l = l;
t[p].r = r;
t[p].len = t[p].sum = 0;
t[p].lc = t[p].rc = 0;
t[p].c = 0;
if (l == r) return;
int mid = (l + r) >> 1;
build(ls, l, mid);
build(rs, mid + 1, r);
}
void push_up(int p)
{
if (t[p].sum)
{
t[p].len = b[t[p].r + 1] - b[t[p].l];
t[p].lc = t[p].rc = 1;
t[p].c = 1;
return ;
}
t[p].len = t[ls].len + t[rs].len;
t[p].lc = t[ls].lc, t[p].rc = t[rs].rc;
t[p].c = t[ls].c + t[rs].c;
if (t[ls].rc && t[rs].lc) t[p].c -= 1;
}
void change(int p, int l, int r, int c)
{
if (b[t[p].r + 1] <= l || r <= b[t[p].l]) return;
if (l <= b[t[p].l] && b[t[p].r + 1] <= r)
{
t[p].sum += c;
push_up(p);
return;
}
change(ls, l, r, c);
change(rs, l, r, c);
push_up(p);
}
ScanLine make_line(int l, int r, int h, int mark)
{
ScanLine res;
res.l = l, res.r = r,
res.h = h, res.mark = mark;
return res;
}
signed main()
{
cin >> n;
for (rint i = 1; i <= n; i++)
{
cin >> x1 >> y1 >> x2 >> y2;
b[2 * i - 1] = x1;
b[2 * i] = x2;
line[2 * i - 1] = {x1, x2, y1, 1};
line[2 * i] = {x1, x2, y2, -1};
}
n <<= 1;
sort(line + 1, line + n + 1);
sort(b + 1, b + n + 1);
int tot = unique(b + 1, b + n + 1) - b - 1;
build(1, 1, tot - 1);
int ans = 0;
for (rint i = 1; i < n; i++)
{
change(1, line[i].l, line[i].r, line[i].mark);
ans += abs(pre - t[1].len);
pre = t[1].len;
ans += 2 * t[1].c * (line[i + 1].h - line[i].h);
}
ans += line[n].r - line[n].l;
cout << ans << endl;
return 0;
}
P9478 [NOI2023] 方格染色
分别算出所有直线和斜线的面积并。前者用扫描线,后者暴力求解即可。
此时我们还要减掉斜线和直线的交点数。我们可以对于每条斜线,枚举每一条直线,判断两者是否有交点。
由于某些交点可能会被扫描两次,用 map
维护交点是否被计算过。
#include <bits/stdc++.h>
#define rint register int
#define int long long
#define endl '\n'
#define mp make_pair
using namespace std;
const int N = 4e5 + 5;
const int Z = 4e6 + 5;
const int M = 1e3 + 5;
int c, n, m, q;
int tot1, tot2, tot3;
int cnt;
int a[N << 1], ans;
vector<int> v[N];
map<pair<int, int>, int> p;
struct ScanLine
{
int l, r, h;
int mark;
friend bool operator < (ScanLine a, ScanLine b)
{
return a.h < b.h;
}
} b[N << 1], line[M];
struct Node
{
int l, r;
int sum;
int len;
} t[Z];
#define ls p << 1
#define rs p << 1 | 1
void build(int p, int l, int r)
{
t[p].l = l;
t[p].r = r;
t[p].len = t[p].sum = 0;
if (l == r) return;
int mid = (l + r) >> 1;
build(ls, l, mid);
build(rs, mid + 1, r);
}
int find(int t)
{
int l = 1, r = tot2;
while (l < r)
{
int mid = (l + r) >> 1;
if (a[mid] >= t) r = mid;
else l = mid + 1;
}
return l;
}
void push_up(int p)
{
if (t[p].sum) t[p].len = a[t[p].r + 1] - a[t[p].l];
else t[p].len = t[ls].len + t[rs].len;
}
void change(int p, int l, int r, int c)
{
if (t[p].l == l && t[p].r == r)
{
t[p].sum += c;
push_up(p);
return ;
}
int mid = (t[p].l + t[p].r) >> 1;
if (r <= mid) change(ls, l, r, c);
else if (l > mid) change(rs, l, r, c);
else change(ls, l, mid, c), change(rs, mid + 1, r, c);
push_up(p);
}
signed main()
{
cin >> c >> n >> m >> q;
while (q--)
{
int t;
cin >> t;
int x1, x2, y1, y2;
cin >> x1 >> y1 >> x2 >> y2;
if (x1 != x2 && y1 != y2)
{
line[++cnt].l = x1;
line[cnt].r = x2;
line[cnt].h = y1;
continue;
}
tot1++;
b[tot1 * 2 - 1] = {x1, x2, y1, 1};
b[tot1 * 2] = {x1, x2, y2, -1};
a[tot1 * 2 - 1] = x1;
a[tot1 * 2] = x2;
}
while (1)
{
int CNT = 0;
for (rint i = 1; i <= cnt; i++)
{
for (rint j = 1; j <= cnt; j++)
{
if (j != i && line[i].h && line[j].h && line[i].h - line[i].l == line[j].h - line[j].l)
{
if (line[j].l >= line[i].l && line[j].r <= line[i].r || line[i].l <= line[j].l && line[j].l <= line[i].r && line[i].r <= line[j].r)
{
CNT++;
line[i].l = min(line[i].l, line[j].l);
line[i].r = max(line[i].r, line[j].r);
line[i].h = min(line[i].h, line[j].h);
line[j].h = 0;
}
}
}
}
if (!CNT) break;
}
for (rint i = 1; i <= cnt; i++)
{
if (line[i].h)
{
ans += line[i].r - line[i].l + 1;
}
}
for (rint i = 1; i <= 2 * tot1; i += 2)
{
if (b[i].h == b[i + 1].h)
{
if (!p[mp(b[i].h, 0)])
{
p[mp(b[i].h, 0)] = ++tot3;
}
v[p[mp(b[i].h, 0)]].push_back(i);
}
else if (b[i].l == b[i].r)
{
if (!p[mp(0, b[i].l)])
{
p[mp(0, b[i].l)] = ++tot3;
}
v[p[mp(0, b[i].l)]].push_back(i);
}
}
for (rint i = 1; i <= cnt; i++)
{
if (!line[i].h) continue;
for (rint j = 1; j <= 2 * tot1; j += 2)
{
int x = b[j].h - line[i].h + line[i].l;
int y = b[j].l + line[i].h - line[i].l;
if (b[j].l == b[j].r && b[j].l >= line[i].l && b[j].l <= line[i].r && y >= b[j].h && y <= b[j + 1].h)
{
if (!p[mp(b[j].l, y)])
{
p[mp(b[j].l, y)] = 1;
ans--;
}
}
else if (b[j].h == b[j + 1].h && b[j].h >= line[i].h && b[j].h <= line[i].h + line[i].r - line[i].l && x >= b[j].l && x <= b[j].r)
{
if (!p[mp(x, b[j].h)])
{
p[mp(x, b[j].h)] = 1;
ans--;
}
}
}
}
for (rint i = 1; i <= 2 * tot1; i++)
{
b[i].l--;
if (i & 1)
{
b[i].h--;
a[i]--;
}
}
sort(a + 1, a + 1 + 2 * tot1);
sort(b + 1, b + 1 + 2 * tot1);
tot2 = unique(a + 1, a + 1 + 2 * tot1) - a - 1;
build(1, 1, tot2);
for (rint i = 1; i < 2 * tot1; i++)
{
int l = find(b[i].l);
int r = find(b[i].r) - 1;
change(1, l, r, b[i].mark);
ans += t[1].len * (b[i + 1].h - b[i].h);
}
cout << ans << endl;
return 0;
}
本文作者:PassName
本文链接:https://www.cnblogs.com/spaceswalker/p/18162401
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步