zoukankan      html  css  js  c++  java
  • 线段树的一些经典操作

    线段树

    ​ (线段树就长这样)

    一些性质

    ​ 1.左儿子是父节点编号的二倍,右儿子是父节点编号的二倍加一。

    #define ls(o) (o << 1)
    #define rs(o) (o << 1 | 1)
    

    ​ 2.线段树数组长度不能小于4N。

    ​ 3.对于一个区间[\(l\), \(r\)], 它的左儿子是[\(l\), \(mid\)], 右儿子是[\(mid\) + 1, \(r\)]。

    #define mid ((l + r) >> 1)
    

    ​ 4.线段树去掉最后一层一定是完全二叉树,深度为\(logn\)

    建树

    void up(int o) {
        t[o].dat = t[ls(o)].dat + t[rs(o)].dat; //从下往上走的时候子节点更新父节点
    }
    
    void build(int o, int l ,int r) {
        if(l == r) {t[o].dat = read(); return ;} //递归到叶子节点
        build(ls(o), l, mid), build(rs(o), mid + 1, r); //递归左右子树
        up(o);
    }
    
    build(1, 1, n); //调用入口
    

    单点修改

    void change(int o, int l ,int r, int x, int k) { //将x位置的值改为k
    	if(l == r) { t[o].dat = k; return ;} //找到x位置,修改
        if(x <= mid) change(ls(o), l, mid, x, k);
        if(x > mid) change(rs(o), mid + 1, r, x, k);
    	up(o); //一定记得要更新
    }
    

    区间查询

    int ask(int o, int l, int r, int x, int y) {
    	if(x <= l && r <= y) return t[o].dat; //当覆盖了一个完整的区间,直接返回这个区间的值,这就是线段树为啥很快
        int res = 0; 
        if(x <= mid) res += ask(ls(o), l, mid, x, y); //一定注意是x <= mid, 不是l <= mid,我在这里死了好几次了
        if(y > mid) res += ask(rs(o), mid + 1, r, x, y);
        return res;
    } 
    

    区间修改

    void modify(int o, int l, int r, int k) {
    	tag[o] += k; t[o].dat += (r - l + 1) * k;
    }
    
    void down(int o, int l, int r) {
    	if(tag[o] != 0) modify(ls(o), l, mid, tag[o]), modify(rs(o), mid + 1,r, tag[o]), tag[o] = 0;
    }
    
    void change(int o, int l, int r, int x, int y, int k) {
    	if(x <= l && r <= y) { return modify(o, l, r, k); }
        down(o, l, r);
        if(x <= mid) change(ls(o), l, mid, x, y, k);
        if(y > mid) changr(rs(o), mid + 1, r, x, y, k);
        up(o); //记得更新
    }
    

    ​ 区间修改的时候我们引入懒标记\(tag\)数组。

    ​ 我们修改区间[\(l\), \(r\)]时,以该节点为根的子树所有点都要修改,复杂度为\(O(n)\)

    ​ 类似区间查询,我们如果发现[\(l\), \(r\)]包含在修改区间中,我们直接给这个节点打上\(tag\)懒标记,然后返回,等到后续的指令里需要从该节点向下递归时,我们直接下传标记,使两个子节点具有标记,然后该节点标记清零

    ​ 使用懒标记后复杂度降为\(O(logn)\)

    带修改最大子段和

    P4513 小白逛公园

    void up(int o) {
        t[o].dat = t[ls(o)].dat + t[rs(o)].dat;
        t[o].l = max(t[ls(o)].l, t[ls(o)].dat + t[rs(o)].l);
        t[o].r = max(t[rs(o)].r, t[rs(o)].dat + t[ls(o)].r);
        t[o].s = max(max(t[ls(o)].s, t[rs(o)].s), t[ls(o)].r + t[rs(o)].l);
    }
    

    ​ 这道题求带修改最大子段和。

    ​ 我们考虑线段树多维护一点东西,分别是紧靠左端的最大子段和,紧靠右端的最大子段和,最大子段和。

    ​ 维护紧靠左端最大子段和:将 左子树紧靠左端的最大子段和左子树的所有点的和+右子树紧靠左端的最大子段和\(max\)。(维护紧靠右端最大子段和也同理)


    ​ 维护最大子段和:将 左子树最大子段和右子树最大子段和左子树紧靠右端子段和+右子树紧靠左端子段和\(max\)

    区间染色

    poj 2777

    ​ 题目大意:有一个长度为\(L\)的色板,可均匀地分为\(L\)个小格。

    ​ 有两种操作:\(C\) \(l\) \(r\) \(x\) 表示把区间[\(l\), \(r\)]染为颜色\(x\) (\(x\) <= 30);

    \(P\) \(l\) \(r\) 表示询问区间[\(l\), \(r\)]有几种颜色。

    ​ 这道题的不同点在于它新的颜色会覆盖掉原来的颜色,但是我们发现它的颜色个数很少,我们可以用二进制压缩来做,每一个线段树上存一个二进制数,第\(i-1\)位为1表示这个区间具有颜色\(i\)

    ​ 在询问颜色个数时,我们可以将每个区间的二进制数用 | 来合并。

    #include <iostream>
    #include <cstdio>
    #define ls(o) (o << 1)
    #define rs(o) (o << 1 | 1)
    #define mid ((l + r) >> 1)
    
    using namespace std;
    
    inline int read() {
    	int s = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') { if(ch == '-') f = -1; ch = getchar(); }
    	while(ch >= '0' && ch <= '9') { s = (s << 1) + (s << 3) + (ch ^ 48); ch = getchar(); }
    	return s * f;
    }
    
    const int N = 1e6 + 5;
    int n, T, m;
    struct tree { int d, tag; } t[N << 2];
    
    void up(int o) {
    	t[o].d = t[ls(o)].d | t[rs(o)].d;
    }
    
    void modify(int o, int l, int r, int k) {
    	t[o].tag = 1; t[o].d = k; //这里是将父节点的颜色都给了子节点,有人可能会想父节点的颜色个数不一定就等于子节点的颜色个数呀。这个函数实际上是在tag不为								 0的时候执行的,也就数说该父节点一定是一种颜色,所以他的子节点也是和父亲节点一样的颜色。
    }
    
    void down(int o, int l, int r) {
    	if(t[o].tag != 0) modify(ls(o), l, mid, t[o].d), modify(rs(o), mid + 1, r, t[o].d), t[o].tag = 0;
    }
    
    void build(int o, int l, int r) {
    	if(l == r) { t[o].tag = t[o].d = 1; return ;}
    	build(ls(o), l, mid); build(rs(o), mid + 1, r);
    	up(o);
    }
    
    void change(int o, int l, int r, int x, int y, int val) {
    	if(x > r || y < l) return ;
    	if(x <= l && y >= r) {
    		t[o].tag = 1; t[o].d = (1 << (val - 1)); return ;  //记得减一
    	}
    	down(o, l, r);
    	if(y <= mid) change(ls(o), l, mid, x, y, val);
    	else if(x > mid) change(rs(o), mid + 1, r, x, y, val);
    	else {
    		change(ls(o), l, mid, x, y, val);
    		change(rs(o), mid + 1, r, x, y, val);
    	}
    	up(o);
    }
    
    int query(int o, int l, int r, int x, int y) {
    	if(x <= l && y >= r) { return t[o].d; } 
    	down(o, l, r);
    	if(y <= mid) return query(ls(o), l, mid, x, y);
    	else if(x > mid) return query(rs(o), mid + 1, r, x, y);
    	else {
    		int a = query(ls(o), l, mid, x, y), b = query(rs(o), mid + 1, r, x, y);
    		return a | b;
    	}
    }
    
    int main() {
    	
    	n = read(); T = read(); m = read();
    	build(1, 1, n);
    	for(int i = 1;i <= m; i++) {
    		char ch; cin >> ch;
    		if(ch == 'C') {
    			int x = read(), y = read(), val = read();
    			if(x > y) swap(x, y); //这里一定要记得写
    			change(1, 1, n, x, y, val);
    		}
    		else {
    			int x = read(), y = read();
    			if(x > y) swap(x, y); //记得写
    			int ans = query(1, 1, n, x, y);
    			int num = 0;
    			while(ans) {
    				if(ans & 1) num++;
    				ans >>= 1; //和快速幂那个差不多,检查哪一位是一,然后记录答案
    			}
    			printf("%d\n", num);
    		}
    	}
    	
    	
    	return 0;
    }
    

    区间第k​小值

    poj 2761

    ​ 题目大意:给定一个长度为\(n\)的序列,有\(m\)条询问,询问区间[\(l\), \(r\)]的第\(k\)小值为多少,没有一个区间完全包含另一个区间,只可能会交叉。

    ​ 这道题的做法挺多的,有平衡树,主席树。线段树也可以做,相对简单一点。

    ​ ''没有一个区间完全包含另一个区间,只可能会交叉。''这句话的意思是一个区间的起点只对应一个终点, 或者说一个区间的终点只对应一个起点。

    ​ 首先离散化。然后我们考虑线段树多维护一点东西,\(t[o].small\)表示在当前有序线段树内小于\(mid\)的个数,也可以说是左子树的大小。

    ​ 对于每一个询问,我们可以把它们都离线下来,按左端点排序,使用两个指针\(x\), \(y\),不断移向新的区间,每加入一个数或丢出一个数就更改线段树来维护\(small\)。找当前询问第\(k\)小值时,递归线段树,如果\(k <= t[o].small\),说明这个第\(k\)小值在\(o\)的左子树内;如果\(k > t[o].small\),说明这个第\(k\)小值在\(o\)的右子树内,那就找右子树的第\(k - t[o].small\)小值。

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #define ls(o) (o << 1)
    #define rs(o) (o << 1 | 1)
    #define mid ((l + r) >> 1)
    
    using namespace std;
    
    inline int read() {
    	int s = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') { if(ch == '-') f = -1; ch = getchar(); }
    	while(ch >= '0' && ch <= '9') { s = (s << 1) + (s << 3) + (ch ^ 48); ch = getchar(); }
    	return s * f;
    }
    
    const int N = 1e5 + 10;
    int n, m, cnt;
    int a[N], b[N], ans[N * 5];
    struct tree { int small; } t[N << 2];
    struct ask { int l, r, k, id; } q[N * 5];
    
    int cmp(ask a, ask b) { return a.l < b.l; }
    
    void change(int o, int l, int r, int x, int flag) {
    	if(l == r) return ;
    	if(x <= mid) t[o].small += flag, change(ls(o), l, mid, x, flag);
    	else change(rs(o), mid + 1, r, x, flag);
    }
    
    int query(int o, int l, int r, int k) {
    	if(l == r) return l;
    	if(k <= t[o].small) return query(ls(o), l, mid, k);
    	else return query(rs(o), mid + 1, r, k - t[o].small);
    }
    
    int main() {
    	
    	n = read(); m = read(); 
    	for(int i = 1;i <= n; i++) b[i] = a[i] = read();
    	sort(b + 1, b + n + 1);
    	int cnt = unique(b + 1, b + n + 1) - b - 1;
    	for(int i = 1;i <= n; i++) {
    		a[i] = lower_bound(b + 1, b + cnt + 1, a[i]) - b;
    	} //离散化
    	for(int i = 1;i <= m; i++) {
    		q[i].l = read(); q[i].r = read(); q[i].k = read(); q[i].id = i;
    	}
    	sort(q + 1, q + m + 1, cmp);
    	int x = q[1].l, y = x;
    	for(int i = 1;i <= m; i++) {
    		while(x < y && x < q[i].l) {
    			change(1, 1, n, a[x], -1); x++; //丢出一个数
    		}
    		if(y < q[i].l) y = q[i].l;
    		while(y <= q[i].r) {
    			change(1, 1, n, a[y], 1); y++; //加入一个数
    		}
    		ans[q[i].id] = b[query(1, 1, n, q[i].k)];
    	}
    	for(int i = 1;i <= m; i++) printf("%d\n", ans[i]); 
    	
    	return 0;
    }
    

    求LIS

    ​ 怎么用线段树求带修改最长上升子序列。

    P4198 楼房重建

    ​ 题目大意:有\(n\)(位置从1到\(n\))个楼房,开始时高度都为零。有\(m\)个操作,每次输入\(x\), \(y\),表示把位置为\(x\)的楼房的高度改为\(y\)。每次操作后都要输出一个数,表示当前修改完后从0位置能看见的楼房个数。

    ​ 很显然,这道题让你求的是最长上升子序列。但不是找楼房高度的最长上升子序列,因为在后面的楼房虽然可能会比前面的高,但不一定会看见,比如:

    (看不见吧)

    ​ 我们发现,只要把高度转化为斜率就好了,是0位置连楼房顶端的直线的斜率,问题变成了求斜率的最长上升子序列。

    ​ 现在考虑线段树维护什么东西。维护一个区间内最长上升子序列,维护一个斜率最大值。斜率最大值比较好转移,直接对左右子树的斜率最大值取\(max\)就好了。那怎么转移区间内最长上升子序列呢?

    ​ 首先,一个区间[\(l\), \(r\)]内的最长上升子序列一定包括左端点\(l\),对于叶子节点它的区间内最长上升子序列是1。

    ​ 设一个区间的最长上升子序列为\(len\),这个区间的\(len\)一定大于等于左儿子的\(len\),因为它一定包含左儿子的左端点,也就是它的左端点。对于它的右儿子,如果它右儿子的左儿子的斜率最大值比它的左儿子的斜率最大值小(或者等于)(有点绕,好好想想),那么就没有它的右儿子的左儿子的事了,直接找右儿子的右儿子,方法和这个相同;如果它右儿子的左儿子的斜率最大值比它的左儿子的斜率最大值大,说明这个区间内对答案有贡献,用同样的方法找右儿子的左儿子,然后加上\(len\)(它的右儿子) - \(len\)(它的右儿子的左儿子)。(这里看不懂的可以画个图理解一下或结合代码理解一下)

    #include <iostream>
    #include <cstdio>
    #define ls(o) (o << 1)
    #define rs(o) (o << 1 | 1)
    #define mid ((l + r) >> 1)
    
    using namespace std;
    
    const int N = 1e5 + 10;
    int n, m;
    double a[N];
    struct tree { double mk; int len; } t[N << 2];
    
    inline int read() {
    	int s = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') { if(ch == '-') f = -1; ch = getchar(); }
    	while(ch >= '0' && ch <= '9') { s = (s << 1) + (s << 3) + (ch ^ 48); ch = getchar(); }
    	return s * f;
    }
    
    void up1(int o) { t[o].mk = max(t[ls(o)].mk, t[rs(o)].mk); }
    
    int up2(int o, int l, int r, double lm) { //传进来的lm一定要开double,要不你都不知道你怎么死的
    	if(t[o].mk <= lm) { return 0; } //如果这个区间的斜率最大值小于等于lm,说明这个区间都不可能对答案有贡献,直接返回0
    	if(a[l] > lm) { return t[o].len; } //如果这个区间内的左端点的斜率比lm大,说明这个区间的len可以直接加到答案里面
    	if(l == r) { return a[l] > lm; } //找到了叶子节点,如果这个点的斜率大于lm,说明这个点对答案有1的贡献
    	if(t[ls(o)].mk <= lm) return up2(rs(o), mid + 1, r, lm); //这里就是上面那一段很绕的话
    	else return up2(ls(o), l, mid, lm) + t[o].len - t[ls(o)].len;
    }
    
    void change(int o, int l, int r, int x, int h) {
    	if(l == r) { t[o].mk = (double)h / x; t[o].len = 1; return ; }
    	if(x <= mid) change(ls(o), l, mid, x, h);
    	if(x > mid) change(rs(o), mid + 1, r, x, h);																	
    	up1(o);
    	t[o].len = t[ls(o)].len + up2(rs(o), mid + 1, r, t[ls(o)].mk);
    }
    
    int main() {
    	
    	n = read(); m = read();
    	for(int i = 1;i <= m; i++) {
    		int x = read(), y = read();
    		a[x] = (double)y / x;
    		change(1, 1, n, x, y);
    		printf("%d\n", t[1].len); 	
    	}
    	
    	return 0;
    }
    

    区间面积并

    P5490 【模板】扫描线

    ​ 题目大意:给你\(n\)个矩形,让你求这\(n\)个矩形覆盖的面积是多少,重叠部分只算一次。

    ​ 我们从下往上扫描一遍,只要碰到矩形的上底或下底就记录一下,将扫描到的线记为一个四元组\((x1, x2, h, flag)\),分别表示这一条线的左端点,右端点,纵坐标,是下底还是上底(下底为1,上底为-1)。(个数是\(2n\)个)

    ​ 首先肯定要离散化,我们设\(X[x]\)表示\(x\)被离散化后的值。

    ​ 然后我们考虑线段树怎么用,让线段树维护一个长度\((len)\)和权值\((sum)\),权值表示该节点自身被覆盖的次数,长度表示该节点被矩形覆盖的长度。当从下往上扫,扫到第一条线的时候,1, 2节点会被更新;扫到第二条线的时候,1, 2, 3, 5, 6节点就会被更新。只要这个节点的权值大于0,那么它的就可以被算到面积里。

    \(S = \displaystyle \sum_{i = 1}^{n - 1} t[1].len * (h[i + 1] - h[i])\)

    ​ 那么怎么更新\(len\)呢?如果\(t[o].sum != 0\),说明这个节点被矩形覆盖(完全覆盖)了,\(t[o].len = x_r - x_l\)就可以了;如果这个节点没有被矩形完全覆盖,直接把它的左右儿子的\(len\)加起来就好了。

    ​ 得注意的是线段树上节点2管到的区间是[1, 2],节点3管到的区间是[3, 3]。为啥没有4,因为4个点只有三段。

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #define ls(o) (o << 1)
    #define rs(o) (o << 1 | 1)
    #define mid ((l + r) >> 1)
    
    using namespace std;
    
    inline long long read() {
    	long long s = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') { if(ch == '-') f = -1; ch = getchar(); }
    	while(ch >= '0' && ch <= '9') { s = (s << 1) + (s << 3) + (ch ^ 48); ch = getchar(); }
    	return s * f;
    }
    
    const int N = 1e6 + 5; //题目数据给的1e5,但是开1e6才能过去,要不就二十分,并且是WA不是RE,就很离谱
    int n, maxn, X[N << 1], raw[N << 1];
    long long ans;
    struct Line {
    	int l, r, h, flag;
    	friend bool operator < (Line a, Line b) { return a.h < b.h; } 
    } line[N << 1];
    struct tree { int len, sum; } t[N << 3];
    
    void up(int o, int l, int r) {
    	if(t[o].sum != 0) t[o].len = raw[r + 1] - raw[l]; //这里注意加一
    	else t[o].len = t[ls(o)].len + t[rs(o)].len;
    }
    
    void change(int o, int l, int r, int x, int y, int k) {
    	if(x <= l && y >= r) { t[o].sum += k; up(o, l, r); return ;}
    	if(x <= mid) change(ls(o), l, mid, x, y, k);
    	if(y > mid) change(rs(o), mid + 1, r, x, y, k);
    	up(o, l, r);
    }
    
    int main() {
    	
    	n = read();
    	for(int i = 1;i <= n; i++) {
    		int x, y, z, u;
    		x = read(); y = read(); z = read(); u = read();
    		line[i * 2 - 1] = (Line) {x, z, y, 1};
    		line[i * 2] = (Line) {x, z, u, -1};													
    		X[i * 2 - 1] = x; X[i * 2] = z;
    	}
    	n <<= 1;
    	sort(X + 1, X + n + 1);
    	int cnt = unique(X + 1, X + n + 1) - X - 1;
    	for(int i = 1;i <= n; i++) {
    		int pos1 = lower_bound(X + 1, X + cnt + 1, line[i].r) - X;
    		int pos2 = lower_bound(X + 1, X + cnt + 1, line[i].l) - X;
    		raw[pos1] = line[i].r; raw[pos2] = line[i].l; //原坐标
    		line[i].r = pos1, line[i].l = pos2; //离散化后,是上面的X数组
    	}
    	sort(line + 1, line + n + 1);
    	for(int i = 1;i < n; i++) { //最后一条线肯定不用算
    		change(1, 1, n, line[i].l, line[i].r - 1, line[i].flag); //这里为啥要减一呢?因为线段树的两个节点不可能重合,比如[1, 5], [6, 9],而																	  不能写成[1, 5][5, 9],但图中是重合的,所以要减一。
    		ans += t[1].len * 1ll * (line[i + 1].h - line[i].h);
    	}
    	printf("%lld", ans);
    	
    	return 0;
    }
    

    区间排序

    P2824 [HEOI2016/TJOI2016]排序

    ​ 题目大意:给你一个1到\(n\)的排列,有\(m\)次操作。“\(0 \ l \ r\)”表示将区间[\(l\), \(r\)]的数升序排列;“\(1 \ l \ r\)”表示将区间[\(l\), \(r\)]的数降序排列。最后询问位置\(q\)上的数是多少。

    ​ 我们可以二分位置\(q\)上的数,设为\(mid\)。将序列里小于\(mid\)的数看成0,将大于等于\(mid\)的数看成1。对于每次排序,我们直接排序0和1就好。说是排序,其实就是把1放前面(后面),把0放后面(前面)。比如现在是升序排列区间[\(l\), \(r\)],我们只要知道了此区间1的个数,那么就可以将[\(l\), \(r - cnt + 1\) ]全部附盖为0,将[\(r - cnt\)\(r\)]全部覆盖为1,于是问题就转化成了区间修改,区间查询,单点查询。

    ​ 所以线段树维护一个区间1的个数就好啦。

    ​ 还有一个注意的地方,将区间全部覆盖为0和全部覆盖为1的\(tag\)值不同,注意区分就好了。

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    #define ls(o) (o << 1)
    #define rs(o) (o << 1 | 1)
    #define mid ((l + r) >> 1)
    
    using namespace std;
    
    inline int read() {
    	int s = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') { if(ch == '-') f = -1; ch = getchar(); }
    	while(ch >= '0' && ch <= '9') { s = (s << 1) + (s << 3) + (ch ^ 48); ch = getchar(); }
    	return s * f;
    }
    
    const int N = 1e5 + 5;
    int n, m, p, ans;
    int a[N], b[N];
    struct tree { 
    	int tag, one; 
    	tree():one(0) {}
    } t[N << 2];
    struct que { int opt, x, y; } q[N];
    
    void up(int o) {
    	t[o].one = t[ls(o)].one + t[rs(o)].one;
    }
    
    void modify(int o, int l, int r, int k) {
    	t[o].tag = k; 
    	if(k == 1) t[o].one = r - l + 1;  else t[o].one = 0;
    }
    
    void down(int o, int l, int r) {
    	if(t[o].tag == 0) return ;
    	if(t[o].tag == 1) { modify(ls(o), l, mid, 1), modify(rs(o), mid + 1, r, 1); }
    	if(t[o].tag == -1) { modify(ls(o), l, mid, -1), modify(rs(o), mid + 1, r, -1); }
    	t[o].tag = 0;
    }
    
    void build(int o, int l, int r, int x) {
    	if(l == r) { t[o].one = a[l] >= x; t[o].tag = 0; return ; }
    	build(ls(o), l, mid, x); build(rs(o), mid + 1, r, x);
    	up(o); t[o].tag = 0;
    }
    
    void change1(int o, int l, int r, int x, int y, int val) {
    	if(x <= l && y >= r) { t[o].one = val * (r - l + 1); t[o].tag = val ? 1 : -1; return ; }
    	if(x > r || y < l) return ;
    	down(o, l, r);
    	if(x <= mid) change1(ls(o), l, mid, x, y, val);
    	if(y > mid) change1(rs(o), mid + 1, r, x, y, val);
    	up(o);
    }
    
    int query1(int o, int l, int r, int x, int y) {
    	if(x <= l && y >= r) { return t[o].one; }
    	if(x > r || y < l) return 0; 
    	down(o, l, r);
    	int res = 0;
    	if(x <= mid) res += query1(ls(o), l, mid, x, y);
    	if(y > mid) res += query1(rs(o), mid + 1, r, x, y);
    	return res;
    }
    
    int query2(int o, int l, int r, int p) {
    	if(l == r && r == p) {return  t[o].one; }
    	down(o, l, r);
    	if(p <= mid) return query2(ls(o), l, mid, p);
    	if(p > mid) return query2(rs(o), mid + 1, r, p);
    }
    
    int judge(int Mid) {
    	build(1, 1, n, Mid);
    	for(int i = 1;i <= m; i++) {
    		int cnt = query1(1, 1, n, q[i].x, q[i].y);
    		if(q[i].opt == 1) {
    			change1(1, 1, n, q[i].x, q[i].x + cnt - 1, 1);
    			change1(1, 1, n, q[i].x + cnt, q[i].y, 0);
    		}
    		else {
    			change1(1, 1, n, q[i].y - cnt + 1, q[i].y, 1);
    			change1(1, 1, n, q[i].x, q[i].y - cnt, 0);
    		}
    	}
    	return query2(1, 1, n, p);
    }
    
    int main() {
    	
    	freopen("seq.in","r",stdin);
    	freopen("seq.out","w",stdout);
    	
    	n = read(); m = read();
    	for(int i = 1;i <= n; i++) a[i] = read();
    	for(int i = 1;i <= m; i++) { q[i].opt = read(); q[i].x = read(); q[i].y = read(); }
    	p = read();
    	int ll = 1, rr = n;
    	while(ll <= rr) {
    		int midd = (ll + rr) >> 1;
    		if(judge(midd)) ans = midd, ll = midd + 1;
    		else rr = midd - 1;
    	}
    	printf("%d", ans);
    	
    	fclose(stdin); fclose(stdout);
    	return 0;	
    }
    

    区间开方

    P4145 上帝造题的七分钟2 / 花神游历各国

    ​ 题目大意:\(n\)个数,\(m\)个操作:“0,l,r”表示将区间[\(l\), \(r\)]的每个数开方,“1,l,r”表示询问区间[\(l\),\(r\)]的数的总和。

    ​ 对于一个\(10^{12}\)大小的数,开方6次就可以变为1(自己可以用计算器试一试)。我们用线段树写的话,维护一个区间和,再维护一个开方次数就好了。对于一个开方过6次的区间,我们直接返回区间和就好了;如果开方次数小于等于6次,那就递归到叶子节点,暴力开方就好。

    ​ 我们发现,如果一个区间的和小于等于区间长度,那么这个区间一定被开方了6次以上,那么我们就可以少维护一个开方次数,直接维护一个区间和就行了,递归的时候我们如果发现\(t[o].sum <= r - l + 1\),直接返回,用不着接着向下递归了(具体看代码吧)。

    #include <iostream>
    #include <cstdio>
    #include <cmath>
    #define ls(o) (o << 1) 
    #define rs(o) (o << 1 | 1)
    #define mid ((l + r) >> 1)
    
    using namespace std;
    
    inline long long read() {
    	long long s = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') { if(ch == '-') f = -1; ch = getchar(); }
    	while(ch >= '0' && ch <= '9') { s = (s << 1) + (s << 3) + (ch ^ 48); ch = getchar(); }
    	return s * f;	
    }
    
    const int N = 5e5 + 5;
    long long n, m;
    struct tree { long long sum; } t[N << 2];
    
    void up(int o) {
    	t[o].sum = t[ls(o)].sum + t[rs(o)].sum;
    }
    
    void build(int o, int l, int r) {
    	if(l == r) { t[o].sum = read(); return ;}
    	build(ls(o), l, mid); build(rs(o), mid + 1, r);
    	up(o);
    }
    
    void change(int o, int l, int r, int x, int y) {
    	if(l == r) { t[o].sum = sqrt(t[o].sum); return ; }
    	if(t[o].sum <= r - l + 1) { return ; }
    	if(x <= mid) change(ls(o), l, mid, x, y);
    	if(y > mid) change(rs(o), mid + 1, r, x, y);
    	up(o);
    }
     
    long long query(int o, int l, int r, int x, int y) {
    	if(x <= l && y >= r) { return t[o].sum; }
    	long long res = 0;
    	if(x <= mid) res += query(ls(o), l, mid, x, y);
    	if(y > mid) res += query(rs(o), mid + 1, r, x, y);
    	return res;
    }
     
    int main() {
    	
    	n = read(); 
    	build(1, 1, n);
    	m = read();
    	for(int i = 1, opt, x, y;i <= m; i++) {
    		opt = read(); x = read(); y = read();
    		if(x > y) swap(x, y); //一定记得判断一下
    		if(opt == 0) {
    			change(1, 1, n, x, y);
     		}
    		else {
    			printf("%lld\n", query(1, 1, n, x, y));
    		}
    	}
    	
    	return 0;
    }
    

    摇花手飞走(233 )

  • 相关阅读:
    日历
    复数的运算
    大数的计算
    poj 1562
    POJ 1002
    利用正则表达式检测违禁字
    js实现一个闹钟
    jQuery实现五星好评
    jquery实现计算器功能
    横向轮播图
  • 原文地址:https://www.cnblogs.com/czhui666/p/13352838.html
Copyright © 2011-2022 走看看