线段树
区间的性质是可以进行分解的,
大的区间的性质可以由小的区间的性质进行转换
建树(build) 利用最大长度进行建树,利用数组或者结构体的编号去建树,标明左右节点的编号
访问(query) 利用递归去访问以该节点为根节点的子树
修改(modify) 递归继续修改,从底向上,当每一次进行完递归操作后进行一次pushup操作更新该点的值
pushup 用该节点的 子节点 去更新该节点的值
( a << 1) + 1 == a << 1 | 1
a << 1 + 1 != a << 1 | 1
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 200010;
struct node{
int l, r;
int v;
}tr[4 * N];
void pushup (int u){
tr[u].v = max(tr[u << 1].v, tr[u << 1 | 1].v);
}
void build (int u, int l, int r){
if(l == r)
tr[u] = {l, r};
else{
tr[u] = {l, r};
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
}
return ;
}
int query (int u, int l, int r){
if(tr[u].l == l && tr[u].r == r) return tr[u].v;//?
int all = 0;
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid)
all = query(u << 1, l, mid);
if(r >= (mid + 1))
all = max(all, query(u << 1 | 1, mid + 1, r));
return all;
}
void modify (int u, int x, int v){
if(tr[u].l == x && tr[u].r == x){
tr[u].v = v;
return ;
}
int mid = tr[u].l + tr[u].r >> 1;
if(x <= mid) modify(u << 1, x, v);
else modify(u << 1 | 1, x, v);
pushup(u);
}
int main()
{ int m, p;
int n = 0, last = 0;
scanf("%d%d", &m, &p);
build(1, 1, m);
int x;
char op[2];
while (m -- )
{
scanf("%s%d", op, &x);
if (*op == 'Q')
{
last = query (1, n - x + 1, n);
printf("%d\n", last);
}
else
{
modify(1, ++ n, (last + x) % p);
}
}
return 0;
}
这样的区间查询就不对
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 200010;
struct node{
int l, r;
int v;
}tr[4 * N];
void pushup (int u){
tr[u].v = max(tr[u << 1].v, tr[u << 1 | 1].v);
}
void build (int u, int l, int r){
if(l == r)
tr[u] = {l, r};
else{
tr[u] = {l, r};
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
}
return ;
}
int query (int u, int l, int r){
if(tr[u].l >= l && tr[u].r <= r) return tr[u].v;//?
int all = 0;
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid)
all = query(u << 1, l, r);
if(r >= (mid + 1))
all = max(all, query(u << 1 | 1, l, r));
return all;
}
void modify (int u, int x, int v){
if(tr[u].l == x && tr[u].r == x){
tr[u].v = v;
return ;
}
int mid = tr[u].l + tr[u].r >> 1;
if(x <= mid) modify(u << 1, x, v);
else modify(u << 1 | 1, x, v);
pushup(u);
}
int main()
{ int m, p;
int n = 0, last = 0;
scanf("%d%d", &m, &p);
build(1, 1, m);
int x;
char op[2];
while (m -- )
{
scanf("%s%d", op, &x);
if (*op == 'Q')
{
last = query(1, n - x + 1, n);
printf("%d\n", last);
}
else
{
modify(1, n + 1, (last + x) % p);
n ++ ;
}
}
return 0;
}
这样就对了,不知道为啥
其实想清楚怎么维护就行了,其他的就是线段树的板子、
感觉线段树有点像动态规划的感觉,就是维护的过程和访问的过程,
树中每个节点和起直接相连的节点之间都有固定的一些关系
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 5e5 + 500;
int n, m;
int w[N];
struct node{
int l, r;
int lmax, rmax, all, xmax;
}tr[4 * N];
void pushup(node &u, node &l, node &r){//重载,这样就可以用于代替
u.all = l.all + r.all;
u.lmax = max(l.lmax, l.all + r.lmax);
u.rmax = max(r.rmax, l.rmax + r.all);
u.xmax = max(max(l.xmax, r.xmax), l.rmax + r.lmax);
}
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[r], w[r], w[r], w[r]};
}
else{
tr[u] = {l, r};
int mid = (l + r ) >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
pushup(u);//得进行回溯,去更新根节点的最大值
}
}
void modify(int u, int x, int v){
if(tr[u].l == x && tr[u].r == x){
tr[u] = {x, x, v, v, v, v};
return;
}
int mid = (tr[u].l + tr[u].r) >> 1;
if(x <= mid) modify(u << 1, x, v);
else modify(u << 1 | 1, x, v);
pushup(u);
}
node query(int u, int l, int r){
if(l <= tr[u].l && r >= tr[u].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{
auto nol = query(u << 1, l, r);
auto nor = query(u << 1 | 1, l, r);
node no ;
pushup(no, nol, nor);
return no;
}
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
build(1, 1, n);
int k, x, y;
while (m -- )
{
scanf("%d%d%d", &k, &x, &y);
if (k == 1)
{
if (x > y) swap(x, y);
printf("%d\n", query(1, x, y).xmax);
}
else modify(1, x, y);
}
return 0;
}
最大公约数
维护最大公约数,其实感觉是一个数学题,
a1, a2, a3, a4 之间的互相的最大公约数 = a1, (a2 - a1), (a3 - a2), (a4 - a3)之间互相的最大公约数
维护一个差分数组,区间加建就可以变成 O(1) 的复杂度
以上都是只是 pushup 的,没有 pushdown 操作
pushdown + 区间查询板子
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define LL long long
using namespace std;
const int N = 1e5 + 100;
struct node{
int l, r;
LL sum, add;
}tr[4 * N];
LL w[N];
void pushup(int u){
tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
void pushdown(int u){
node &root = tr[u];
node &left = tr[u << 1];
node &right = tr[u << 1 | 1];
if(root.add){//叶子节点
left.add += root.add;
left.sum += (LL)(left.r - left.l + 1) * root.add;
right.add += root.add;
right.sum += (LL)(right.r - right.l + 1) * root.add;
root.add = 0;
}
}
void build(int u, int l, int r){
if(l == r) {
tr[u] = {l, r, w[l], 0};
return ;
}
tr[u] = {l, r};//别忘了左右节点的增加
int mid = l + r >> 1;
build(u << 1, l, mid);//直接建边就行了
build(u << 1 | 1, mid + 1, r);
pushup(u);//在这里更新结点的值
}
LL query(int u, int l, int r){
if(l <= tr[u].l && tr[u].r <= r)
return tr[u].sum;
pushdown(u);//先传递add值,更新子节点的sum值
int mid = tr[u].l + tr[u].r >> 1;
LL sum = 0;//long long
if(l <= mid) sum += query(u << 1, l, r);
if((mid + 1) <= r) sum += query(u << 1 | 1, l, r);
return sum;
}
void modify(int u, int l, int r, int d){//区间更新,不是一个节点,而是一段
if(l <= tr[u].l && tr[u].r <= r){
tr[u].add += d;
tr[u].sum += (LL)(tr[u].r - tr[u].l + 1) * d;
return ;
}
pushdown(u);//先传递add值,更新子节点的sum值
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) modify(u << 1, l, r, d);
if(mid < r) modify(u << 1 | 1, l, r, d);
pushup(u);//修改完子区间后修改该区间的值
}
int main(){
int n, m;
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i ++) scanf("%lld",&w[i]);
build(1, 1, n);
while(m --){
char ch[2];
int a, b;
scanf("%s%d%d",ch, &a, &b);
if(*ch == 'Q') printf("%lld\n",query(1, a, b));//注意是long long
else {
int d;
scanf("%d",&d);
modify(1, a, b, d);
}
}
return 0;
}
求矩形面积的和
操作一将区间[l, j] + k
操作而查询整个区间内长度大于0的区间总长是多少
线段树中节点信息,
1, cnt, 当前区间整个被覆盖的次数
2, len不考虑祖先节点cnt的前提下从cnt > 0 的区间总长
永远只考虑根节点的信息, 不会有pushdown的操作
所有操作均是成对出现,且先加后减
用x轴建线段树,
把所有的坐标先全存进去,矩形的左边设权值为1,右边的权值设为-1,这样进行矩形的加减,
为了更好的建树,把x坐标进行离散化,把他们变成连续的整数,这样建树的时候左右的节点的值和普通线段树的分布是一样的
这个代码不用pushdown是因为不用向下进行传值,每次进行计算矩形面积的时候用的只是根节点的cnt值,
因为所有的边都是对称的,添加后必然进行删除操作,而且添加和删除的时候的线段树访问的节点都是一样的,
所以并不用pushdown操作
#include <cstdio>//照着ac代码敲的,并不是自己写的
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 100010;
int n;
struct Segment{//记录边
double x, y1, y2;
int k;
bool operator < (const Segment &t)const{
return x < t.x;
}
}seg[N * 2];
struct Node{//线段树
int l, r;
int cnt;
double len;
}tr[N * 8];
vector <double> ys;//线段树中节点,离散化
int find(double y){
return lower_bound(ys.begin(), ys.end(), y) - ys.begin();
}
void pushup(int u){
if(tr[u].cnt) tr[u].len = ys[tr[u].r + 1] - ys[tr[u].l];//该区间呗完全包含,计算长度为该区间的长度
else if(tr[u].l != tr[u].r){
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){
tr[u] = {l, r, 0, 0};
if(l != r){
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, 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 T = 1;
while(scanf("%d",&n), n){
ys.clear();
for(int i = 0, j = 0;i < n;i ++){
double x1, x2, y1, y2;
scanf("%lf%lf%lf%lf", &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);//减2是因为首先从零开始,然后因为是区间,而不是点坐标
sort(seg, seg + n * 2);
double res = 0;
for(int i = 0;i < n * 2;i ++){
if(i > 0) res += tr[1].len * (seg[i].x - seg[i-1].x);
modify(1, find(seg[i].y1), find(seg[i].y2) - 1, seg[i].k);
}
printf("Test case #%d\n", T ++ );
printf("Total explored area: %.2lf\n\n", res);
}
return 0;
}
以上是区间的加法操作,还有区间的乘法操作,
区间乘法
得想明白加和乘的先后顺序和操作的方法
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 100010;
int n, p, m;
int w[N];
struct Node
{
int l, r;
int sum, add, mul;
}tr[N * 4];
void pushup(int u)
{
tr[u].sum = (tr[u << 1].sum + tr[u << 1 | 1].sum) % p;
}
void eval(Node &t, int add, int mul)
{
t.sum = ((LL)t.sum * mul + (LL)(t.r - t.l + 1) * add) % p;
t.mul = (LL)t.mul * mul % p;
t.add = ((LL)t.add * mul + add) % p;
}
void pushdown(int u)
{
eval(tr[u << 1], tr[u].add, tr[u].mul);
eval(tr[u << 1 | 1], tr[u].add, tr[u].mul);
tr[u].add = 0, tr[u].mul = 1;
}
void build(int u, int l, int r)
{
if (l == r) tr[u] = {l, r, w[r], 0, 1};
else
{
tr[u] = {l, r, 0, 0, 1};
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
pushup(u);
}
}
void modify(int u, int l, int r, int add, int mul)
{
if (tr[u].l >= l && tr[u].r <= r) eval(tr[u], add, mul);
else
{
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) modify(u << 1, l, r, add, mul);
if (r > mid) modify(u << 1 | 1, l, r, add, mul);
pushup(u);
}
}
int query(int u, int l, int r)
{
if (tr[u].l >= l && tr[u].r <= r) return tr[u].sum;
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
int sum = 0;
if (l <= mid) sum = query(u << 1, l, r);
if (r > mid) sum = (sum + query(u << 1 | 1, l, r)) % p;
return sum;
}
int main()
{
scanf("%d%d", &n, &p);
for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
build(1, 1, n);
scanf("%d", &m);
while (m -- )
{
int t, l, r, d;
scanf("%d%d%d", &t, &l, &r);
if (t == 1)
{
scanf("%d", &d);
modify(1, l, r, 0, d);
}
else if (t == 2)
{
scanf("%d", &d);
modify(1, l, r, d, 1);
}
else printf("%d\n", query(1, l, r));
}
return 0;
}