这一块没有复习,先扔这日后完善。
线段树学习
为什么开4*N空间?
- 线段树开4*N空间证明
- 关于线段树开4倍空间的探讨
- 有效节点数量最多有2N,那么build操作时间最多为2N。但是因为最后一层,使得总最大节点编号可以达到4N。
为什么查询操作时间为 O ( 4 ∗ l o g 2 n ) O(4*log_2{n}) O(4∗log2n) ?
- 从第二层开始,每个区间(最多有两个)都会被分为两块,其中一块继续向下递归,另一块return。 因为有 l o g 2 n log_2{n} log2n 层,因此时间为 O ( 2 ∗ l o g 2 n + 2 ∗ l o g 2 n ) O(2*log_2{n} + 2*log_2{n}) O(2∗log2n+2∗log2n) 。
关于build函数和modify函数:
- 进入函数后分为两类:是否到达叶节点。否则递归两个孩子或者其中一个。
关于query函数:
- 我们要保证从主函数或递归进入query函数时,下一节点的区间和查询区间有交集,否则不进入下一层query。
- 进入函数后分为四类:完全被查询区间覆盖(1种),部分被查询区间覆盖(3种)。
- query返回类型可以看作在此节点,对查询区间[l,r]的数据的整合后返回的对象。因此在Acwing245中就可以返回node,看作孩子节点在[l,r]内的有效范围的一个整合。
Acwing245-你能回答这些问题吗
#include<iostream>
#include<cstdio>
#include<vector>
#include<map>
using namespace std;
const int N=5e5 + 10;
typedef long long LL;
struct node{
int l, r;
int tmax, lmax, rmax, sum;
}tr[4 * N];
void pushup(node & a, node & b, node & c){
a.sum = b.sum + c.sum;
a.lmax = max(b.lmax, b.sum + c.lmax);
a.rmax = max(c.rmax, c.sum + b.rmax);
a.tmax = max(max(b.tmax, c.tmax), b.rmax + c.lmax);
}
void pushup(int u){
pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
}
void build(int u, int l, int r)
{
tr[u] = {l, r};
if(l == r) return ;
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
}
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 + 1) return query(u << 1 | 1, l, r);
else{
node left = query(u << 1, l, r);
node right = query(u << 1 | 1, l, r);
node now; pushup(now, left, right);
return now;
}
}
void modify(int u, int x, int tmax)
{
if(tr[u].l == tr[u].r) tr[u] = {x, x, tmax, tmax, tmax, tmax};
else{
int mid = tr[u].l + tr[u].r >> 1;
if(x <= mid) modify(u << 1, x, tmax);
else modify(u << 1 | 1, x, tmax);
pushup(u);
}
}
int main()
{
int n, m, x, op, y; scanf("%d %d", &n, &m);
build(1, 1, n);
for(int i=1; i<=n; i++)
{
scanf("%d", &x);
modify(1, i, x);
}
while(m--)
{
scanf("%d %d %d", &op, &x, &y);
if(op == 1)
{
if(x > y) swap(x, y);
printf("%d\n", query(1, x, y).tmax);
}
else modify(1, x, y);
}
system("pause");
return 0;
}
Acw246-区间最大公约数
- 原序列的最大公约数等于等差序列的最大公约数,因此维护等差序列。
- 相当于区间修改,单点查询。
#include<iostream>
#include<cstdio>
#include<vector>
#include<map>
using namespace std;
const int N=5e5 + 10;
typedef long long LL;
struct node{
LL l, r;
LL sum, d;
}tr[4 * N];
LL w[N];
LL gcd(LL a, LL b){
if(!b) return a;
return gcd(b, a % b);
}
void pushup(node & a, node & b, node & c){
a.sum = b.sum + c.sum;
a.d = gcd(b.d, c.d);
}
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] - w[l - 1], w[l] - w[l - 1]};
else{
tr[u] = {l, r};
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
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 + 1) return query(u << 1 | 1, l, r);
else{
node left = query(u << 1, l, r);
node right = query(u << 1 | 1, l, r);
node now; pushup(now, left, right);
return now;
}
}
void modify(int u, int x, LL d)
{
if(tr[u].l == x && tr[u].r == x){
LL t = tr[u].sum + d;
tr[u] = {x, x, t, t};
}
else{
int mid = tr[u].l + tr[u].r >> 1;
if(x <= mid) modify(u << 1, x, d);
else modify(u << 1 | 1, x, d);
pushup(u);
}
}
int main()
{
int n, m, l, r; scanf("%d %d", &n, &m);
char op; LL d;
for(int i=1; i<=n; i++) scanf("%lld", &w[i]);
build(1, 1, n);
while(m--)
{
scanf(" %c %d %d", &op, &l, &r);
if(op == 'C')
{
scanf("%lld", &d);
modify(1, l, d);
if(r + 1 <= n) modify(1, r + 1, -d);
}else
{
node left = query(1, 1, l);
if(l == r){
printf("%lld\n", llabs(left.sum));
}else{
node right = query(1, l + 1, r);
printf("%lld\n", llabs(gcd(left.sum, right.d)));
}
}
}
system("pause");
return 0;
}
问题:动态加和动态乘结合起来怎么做?
思路:先乘后加,就容易得到懒标记的递推式,按照递推式pushdown即可。
代码:https://loj.ac/s/1249505
扫描线 + 线段树
一维扫描线:扫描线Sweep Line算法总结
二维扫描线:
文章:一文读懂扫描线算法
关于不用pushdown的思考:
- 一:可以不用pushdown就可以:如果问的是至少覆盖一次的区间长度,那么一个节点对应的区间 1. 只要被完全覆盖一次及以上,其两个孩子区间就不用考虑了。左边界一定要等到另一个和他配对的右边界到来才会消失,期间不可能出现空洞。 2. 没有被完全覆盖,要考虑孩子区间,有效区间从孩子区间转移过来。https://www.acwing.com/solution/content/1051/
- 二:为什么使用普通线段树区间修改的pushdown会错呢?:我们写普通线段树区间修改时,pushdown都不需要使用孩子区间来更新父亲区间,修改信息更新到此区间即可,不向下更新。但是在这道题里面,pushdown要用到孩子区间来更新此区间,就会造出现使用未更新的信息的情况。因此这里会出错。
AC代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N=1e4 + 10;
double y[N * 2];
int pos;
int find(double k) { return lower_bound(y, y + pos, k) - y; }
struct Seg{
double x, y1, y2;
int k;
bool operator < (const Seg & a) const { return x < a.x; }
}seg[N * 2];
struct node{
int l, r;
int cnt;
double len;
}tr[N * 8];
void pushup(int u)
{
if(tr[u].cnt) tr[u].len = y[tr[u].r] - y[tr[u].l];
else if(tr[u].l != tr[u].r - 1) tr[u].len = tr[u << 1].len + tr[u << 1 | 1].len;
else tr[u].len = 0;
}
void build(int u, int l, int r)
{
if(l == r - 1) tr[u] = {l, r, 0, 0};//这里的叶子区间是两个相邻y之间的区间,而不是单点。例:[2,3]
else{
tr[u] = {l, r, 0, 0};
int mid = l + r >> 1;
build(u << 1, l, mid); build(u << 1 | 1, mid, r);
}
}
void modify(int u, int l, int r, int k)
{
if(tr[u].l >= l && tr[u].r <= r)
{
tr[u].cnt += k;
pushup(u);
}else{
int mid = tr[u].l + tr[u].r >> 1;
if(l < mid) modify(u << 1, l, r, k);
if(r > mid) modify(u << 1 | 1, l, r, k);
pushup(u);
}
}
int main()
{
int n, T = 1;
while(scanf("%d", &n), n)
{
double x1, y1, x2, y2;
for(int i=0; i<n; i++)
{
scanf("%lf %lf %lf %lf", &x1, &y1, &x2, &y2);
y[i << 1] = y1, y[i << 1 | 1] = y2;
seg[i << 1] = {x1, y1, y2, 1}; seg[i << 1 | 1] = {x2, y1, y2, -1};
}
sort(y, y + 2 * n); sort(seg, seg + 2 * n);
pos = unique(y, y + 2 * n) - y;
build(1, 0, pos - 1);
double res = 0;
for(int i=0; i<2 * n; i++)
{
if(i) res += (seg[i].x - seg[i - 1].x) * tr[1].len;
modify(1, find(seg[i].y1), find(seg[i].y2), seg[i].k);
}
printf("Test case #%d\nTotal explored area: %.2f\n\n", T++, res);
}
system("pause");
return 0;
}
hdu 1255 覆盖的面积
题解:https://www.cnblogs.com/scau20110726/archive/2013/04/14/3020998.html
思路:题解讲的非常好了。
-
在Atlantis题目中,cnt定义为节点代表区间被完全覆盖的次数,len定义为被覆盖了一次或以上的长度。
-
而在此题中,len1表示该该区间内被覆盖了1次或以上的长度,len2表示被覆盖了2次或以上的长度
-
其中的pushup操作类似于状态转移方程。
-
在Atlantis题目中,一个节点对应的区间 1. 只要被完全覆盖一次及以上,其两个孩子区间就不用考虑了。左边界一定要等到另一个和他配对的右边界到来才会消失,期间不可能出现空洞。2. 没有被完全覆盖,要考虑孩子区间,有效区间从孩子区间转移过来。
-
但是在此题中,有可能出现被完全覆盖,但是也要考虑孩子区间的情况,即非叶节点cnt=1。此时要好好考虑怎么去转移。
AC代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=1010;
typedef long long LL;
double y[N * 2];
int pos;
int find(double k) { return lower_bound(y, y + pos, k) - y; }
struct Seg{
double x, y1, y2;
int k;
bool operator < (const Seg & a) const { return x < a.x; }
}seg[N * 2];
struct node{
int l, r, cnt;
double len1, len2;
}tr[N * 8];
void build(int u, int l, int r)
{
if(l == r - 1) tr[u] = {l, r, 0, 0, 0};
else{
tr[u] = {l, r, 0, 0, 0};
int mid = l + r >> 1;
build(u << 1, l, mid); build(u << 1 | 1, mid, r);
}
}
void pushup(int u)
{
if(tr[u].cnt >= 2) tr[u].len2 = tr[u].len1 = y[tr[u].r] - y[tr[u].l];
else if(tr[u].l == tr[u].r - 1)
{
if(tr[u].cnt) tr[u].len1 = y[tr[u].r] - y[tr[u].l];
else tr[u].len1 = 0;
tr[u].len2 = 0;
}else
{
if(tr[u].cnt){
tr[u].len1 = y[tr[u].r] - y[tr[u].l];
tr[u].len2 = tr[u << 1].len1 + tr[u << 1 | 1].len1;
}
else{
tr[u].len1 = tr[u << 1].len1 + tr[u << 1 | 1].len1;
tr[u].len2 = tr[u << 1].len2 + tr[u << 1 | 1].len2;
}
}
}
void modify(int u, int l, int r, int k)
{
if(tr[u].l >= l && tr[u].r <= r)
{
tr[u].cnt += k;
pushup(u);
}else{
int mid = tr[u].l + tr[u].r >> 1;
if(l < mid) modify(u << 1, l, r, k);
if(r > mid) modify(u << 1 | 1, l, r, k);
pushup(u);
}
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
int n; scanf("%d", &n);
double x1, x2, y1, y2;
for(int i=0; i<n; i++)
{
scanf("%lf %lf %lf %lf", &x1, &y1, &x2, &y2);
y[i << 1] = y1; y[i << 1 | 1] = y2;
seg[i << 1] = {x1, y1, y2, 1}; seg[i << 1 | 1] = {x2, y1, y2, -1};
}
sort(y, y + 2 * n); sort(seg, seg + 2 * n);
pos = unique(y, y + 2 * n) - y;
build(1, 0, pos - 1);
double ans = 0;
for(int i=0; i<2 * n; i++)
{
if(i) ans += (seg[i].x - seg[i - 1].x) * tr[1].len2;
modify(1, find(seg[i].y1), find(seg[i].y2), seg[i].k);
}
printf("%.2f\n", ans);
}
system("pause");
return 0;
}
hdu-1828 Picture
思路:板子题,当区间变化时,变化的长度大小累加起来就是答案,横竖扫一遍。
注意有个坑,两个矩形有一条边重叠,这条边并入矩形并集。那么按以前的扫描线就会出问题。
解决:我们在对seg排序的时候特殊一点,横坐标相同的时候,把入区间排在前面,出区间排在后面,就可以避免先出后入造成的bug。
AC代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=5010;
typedef long long LL;
int y[N * 2];
int pos;
int find(int k) { return lower_bound(y, y + pos, k) - y; }
struct Seg{
int x, y1, y2;
int k;
bool operator < (const Seg & a) const{
if(x != a.x) return x < a.x;
return k > a.k;
}
}seg[N * 2];
struct node{
int l, r, cnt;
int len;
}tr[N * 8];
void build(int u, int l, int r)
{
if(l == r - 1) tr[u] = {l, r, 0, 0};
else{
tr[u] = {l, r, 0, 0};
int mid = l + r >> 1;
build(u << 1, l, mid); build(u << 1 | 1, mid, r);
}
}
void pushup(int u)
{
if(tr[u].cnt) tr[u].len = y[tr[u].r] - y[tr[u].l];
else if(tr[u].l == tr[u].r - 1) tr[u].len = 0;
else tr[u].len = tr[u << 1].len + tr[u << 1 | 1].len;
}
void modify(int u, int l, int r, int k)
{
if(tr[u].l >= l && tr[u].r <= r)
{
tr[u].cnt += k;
pushup(u);
}else{
int mid = tr[u].l + tr[u].r >> 1;
if(l < mid) modify(u << 1, l, r, k);
if(r > mid) modify(u << 1 | 1, l, r, k);
pushup(u);
}
}
pair<int, int>p1[N], p2[N];
int n;
LL sol()
{
int x1, x2, y1, y2;
for(int i=0; i<n; i++)
{
x1 = p1[i].first, y1 = p1[i].second; x2 = p2[i].first, y2 = p2[i].second;
y[i << 1] = y1; y[i << 1 | 1] = y2;
seg[i << 1] = {x1, y1, y2, 1}; seg[i << 1 | 1] = {x2, y1, y2, -1};
}
sort(y, y + 2 * n); sort(seg, seg + 2 * n);
pos = unique(y, y + 2 * n) - y;
build(1, 0, pos - 1);
LL ans = 0, last = 0;
for(int i=0; i<2 * n; i++)
{
modify(1, find(seg[i].y1), find(seg[i].y2), seg[i].k);
ans += llabs(last - tr[1].len);
last = tr[1].len;
// cout<<"## "<<tr[1].len<<endl;
}
return ans;
}
int main()
{
while(scanf("%d", &n)!=EOF)
{
if(!n) { cout<< 0 <<endl; continue; }
for(int i=0; i<n; i++) scanf("%d %d %d %d", &p1[i].first, &p1[i].second, &p2[i].first, &p2[i].second);
LL ans = sol();
for(int i=0; i<n; i++) swap(p1[i].first, p1[i].second), swap(p2[i].first, p2[i].second);
ans += sol();
printf("%lld\n", ans);
}
system("pause");
return 0;
}
/*hack
2
5 4 9 15
9 2 11 14
WA.out
58
AC.out
38
*/
P1502 窗口的星星
思路:洛谷题解大概都有了。每一个星星对应一个带权矩形,权值可累加,然后求平面上最大的点权。
对过去学习的思考:
为什么求矩形面积并不用pushdown呢:
- 之前有写,普通pushdown一般不会用孩子区间更新此区间,但是矩形面积并写pushdown要使用孩子节点的信息,就会出现使用未更新的信息的情况。
为什么这道题要用pushdown呢?
- 这道题要求的是区间最大点权,即信息属性为max,就不能利用矩形面积并的特殊属性——某区间被覆盖就不用考虑孩子区间。而要老老实实的写pushdown。
AC代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=1e4 + 10;
typedef long long LL;
int y[N * 2];
int pos;
int find(int k) { return lower_bound(y, y + pos, k) - y; }
struct Seg{
int x, y1, y2;
LL k;
bool operator < (const Seg & a) const{
if(x != a.x) return x < a.x;
return k > a.k;
}
}seg[N * 2];
struct node{
int l, r;
LL v, add;
}tr[N * 8];
void build(int u, int l, int r)
{
if(l == r) tr[u] = {l, r, 0, 0};
else{
tr[u] = {l, r, 0, 0};
int mid = l + r >> 1;
build(u << 1, l, mid); build(u << 1 | 1, mid + 1, r);
}
}
void pushup(int u)
{
tr[u].v = max(tr[u << 1].v, tr[u << 1 | 1].v);
}
void pushdown(int u)
{
if(tr[u].add)
{
auto & a = tr[u], & b = tr[u << 1], & c = tr[u << 1 | 1];
b.add += a.add; c.add += a.add;
b.v += a.add; c.v += a.add;
a.add = 0;
}
}
void modify(int u, int l, int r, LL add)
{
if(tr[u].l >= l && tr[u].r <= r)
{
tr[u].v += add;
tr[u].add += add;
}else{
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) modify(u << 1, l, r, add);
if(r >= mid + 1) modify(u << 1 | 1, l, r, add);
pushup(u);
}
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
int n, W, H; scanf("%d %d %d", &n, &W, &H);
if(W == 1 || H == 1) { printf("0\n"); continue; }
int xx, yy, l;
for(int i=0; i<n; i++)
{
scanf("%d %d %d", &xx, &yy, &l);
y[i << 1] = yy, y[i << 1 | 1] = yy + H - 1;
seg[i << 1] = {xx, yy, yy + H - 1, l}; seg[i << 1 | 1] = {xx + W - 1, yy, yy + H - 1, -l};
}
// for(int i=0; i<2 * n; i++) cout<<"## "<<y[i]<<endl;
sort(y, y + 2 * n); sort(seg, seg + 2 * n);
pos = unique(y, y + 2 * n) - y;
build(1, 0, pos - 1);
LL ans = -9e18;
for(int i=0; i<2 * n; i++)
{
modify(1, find(seg[i].y1), find(seg[i].y2), seg[i].k);
ans = max(ans, tr[1].v);
// cout<<"# "<<tr[1].v<<" "<<tr[2].v<<" "<<tr[3].v<<endl;
}
printf("%lld\n", ans);
}
system("pause");
return 0;
}
又是一道不看题解做不出来的题。
题解:- 51nod 1559 车和矩形