第五讲 树状数组与线段树

1. 动态求连续区间和(线段树)

在这里插入图片描述
💡💡💡思路:
典型的线段树点修改与区间和


public class Main{
	static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
	static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
	static int N = (int) (1e5 + 10);
	static int[] a = new int[N];
	static node[] t = new node[N*4];
	static int n = 0, m = 0;
	public static void main(String[] args) throws Exception{
		init();
		String[]nm = br.readLine().split(" ");
		n = Integer.parseInt(nm[0]);
		m = Integer.parseInt(nm[1]);
		String[] aa = br.readLine().split(" ");
		for(int i = 1; i <= n; i++) {
			a[i] = Integer.parseInt(aa[i - 1]);
		}
		build(1,1,n);
		for(int i = 1; i <= m; i++) {
			String[]kab = br.readLine().split(" ");
			int k = Integer.parseInt(kab[0]);
			int a = Integer.parseInt(kab[1]);
			int b = Integer.parseInt(kab[2]);
			if(k == 0) {
				System.out.println(query(1,a,b));
			}else {
				update(1,a,a,b);
			}
		}
	}
	private static void update(int p, int l, int r, int d) {
		if(t[p].l >= l && t[p].r <= r) {
			t[p].sum += (t[p].r - t[p].l + 1)*d;
			t[p].add += d;
			return;
		}
		spread(p);
		int mid = (t[p].l + t[p].r) >> 1;
		if(l <= mid) {
			update(p*2, l, r,d);
		}
		if(r > mid) {
			update(p*2+1, l, r,d);
		}
		t[p].sum = t[p*2].sum + t[p*2 + 1].sum;
	}
	private static void spread(int p) {
		if(t[p].add == 0) {
			return;
		}
		t[p*2].sum += t[p].add*(t[p*2].r - t[p*2].l + 1);
		t[p*2 + 1].sum += t[p].add*(t[p*2 + 1].r - t[p*2 + 1].l + 1);
		t[p*2].add += t[p].add;
		t[p*2+1].add += t[p].add;
		t[p].add = 0;
	}
	private static long query(int p, int l, int r) {
		if(t[p].l >= l && t[p].r <= r) {
			return t[p].sum;
		}
		spread(p);
		long val = 0;
		int mid = (t[p].l + t[p].r) >> 1;
		if(l <= mid) {
			val += query(p*2, l, r);
		}
		if(r > mid) {
			val += query(p*2+1, l, r);
		}
		return val;
	}
	private static void init() {
		for(int i = 0; i < N*4; i++) {
			t[i] = new node();
		}
	}
	private static void build(int p, int l, int r) {
		t[p].l = l;
		t[p].r = r;
		if(l == r) {
			t[p].sum = a[l];
			return;
		}
		int mid = (t[p].l + t[p].r) >> 1;
		build(p*2,l,mid);
		build(p*2+1, mid + 1, r);
		t[p].sum = t[p*2].sum + t[p*2+1].sum;
	}
}
class node{
	int l,r;
	int add,sum;
}

2.数星星(树状数组)

3.数列区间最大值(线段树维护区间最大值)

在这里插入图片描述

public class Main{
	static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
	static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
	static int N = (int) (1e5 + 10);
	static int[] a = new int[N];
	static node[] t = new node[N*4];
	static int n = 0, m = 0;
	public static void main(String[] args) throws Exception{
		init();
		String[]nm = br.readLine().split(" ");
		n = Integer.parseInt(nm[0]);
		m = Integer.parseInt(nm[1]);
		String[] aa = br.readLine().split(" ");
		for(int i = 1; i <= n; i++) {
			a[i] = Integer.parseInt(aa[i - 1]);
		}
		build(1,1,n);
		for(int i = 1; i <= m; i++) {
			String[]ab = br.readLine().split(" ");
			int a = Integer.parseInt(ab[0]);
			int b = Integer.parseInt(ab[1]);
			out.println(query(1,a,b));
		}
		out.flush();
	}
	private static long query(int p, int l, int r) {
		if(t[p].l >= l && t[p].r <= r) {
			return t[p].maxn;
		}
		long val = -Integer.MAX_VALUE; //注意初始化
		int mid = (t[p].l + t[p].r) >> 1;
		if(l <= mid) {
			val =Math.max(val, query(p*2, l, r));
		}
		if(r > mid) {
			val = Math.max(val,query(p*2+1, l, r));
		}
		return val;
	}
	private static void init() {
		for(int i = 0; i < N*4; i++) {
			t[i] = new node();
		}
	}
	private static void build(int p, int l, int r) {
		t[p].l = l;
		t[p].r = r;
		if(l == r) {
			t[p].maxn = a[l];
			return;
		}
		int mid = (t[p].l + t[p].r) >> 1;
		build(p*2,l,mid);
		build(p*2+1, mid + 1, r);
		t[p].maxn = Math.max(t[p*2].maxn , t[p*2+1].maxn);
	}
}
class node{
	int l,r;
	int maxn;
}

4.小朋友排队(归并排序/树状数组/冒泡)

在这里插入图片描述

思路:其实就是求每个人前面比它大的,后面比它小的,显然是求逆序对
法1:冒泡排序

显然求解过程符合冒泡排序的原理,每次只允许交换相邻的两个元素,但是这种方法肯定是会T的

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 100010;
struct node {
    int val;
    int k;
}a[N];
signed main() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; i ++) {
        cin >> a[i].val;
        a[i].k = 0;
    }
    for (int i = 1; i <= n - 1; i ++) {
        for (int j = 1; j <= n - 1; j ++) {
            if (a[j].val > a[j+1].val) {
                a[j].k ++;
                a[j+1].k ++;
                node t = a[j];
                a[j] = a[j+1];
                a[j+1] = t;
            }
        }
    }
    int sum = 0;
    for (int i = 1; i <= n; i ++) {
        // cout << a[i].k << ' ';
        sum += a[i].k * (a[i].k + 1) / 2;
    }
    cout << sum;;
}

法2:归并排序
同平时的归并排序求逆序对有所不同,我们需要求前面比它大的,后面比它小的,因此需要进行两次归并排序,升序排序求前面比它大的个数,降序排序求后面比他小的。
需要注意的是java中深拷贝与浅拷贝(深拷贝中基本数据类型是值传递,但是引用类型的话就是指向同一地址空间,所以在对b进行赋值的时候,不能直接将a赋值给b,会导致他俩的操作互相影响)

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 100010;
struct node {
    int val;
    int k;
}a[N];
signed main() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; i ++) {
        cin >> a[i].val;
        a[i].k = 0;
    }
    for (int i = 1; i <= n - 1; i ++) {
        for (int j = 1; j <= n - 1; j ++) {
            if (a[j].val > a[j+1].val) {
                a[j].k ++;
                a[j+1].k ++;
                node t = a[j];
                a[j] = a[j+1];
                a[j+1] = t;
            }
        }
    }
    int sum = 0;
    for (int i = 1; i <= n; i ++) {
        // cout << a[i].k << ' ';
        sum += a[i].k * (a[i].k + 1) / 2;
    }
    cout << sum;;
}

5.油漆面积(线段树/扫描线❗)

在这里插入图片描述
**加粗样式
法1:暴力枚举标记(TLE)
需要注意遍历循环的下标范围
在这里插入图片描述
比如上图说(1,1)和(4,4)为矩形两个对角点,那么我们可以覆盖的范围显然是绿色部分(所以遍历的时候两边只能取到一边,取哪边都是可以的)

for(int x = x1 + 1; x <= x2; x++) {
				for(int y = y1 + 1; y <= y2; y++) {
					if(!vis[x][y]) {
						vis[x][y] = true;
						sum++;
					}
				}
			}

public class Main{
	static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
	static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
	static int N = (int) (1e4 + 10);
	static boolean vis[][] = new boolean[N][N];
	static int n = 0, m = 0;
	static long sum = 0;
	public static void main(String[] args) throws Exception{
		n = Integer.parseInt(br.readLine());
		for(int i = 1; i <= n; i++) {
			String[] xy = br.readLine().split(" ");
			int x1 = Integer.parseInt(xy[0]);
			int y1= Integer.parseInt(xy[1]);
			int x2 = Integer.parseInt(xy[2]);
			int y2 = Integer.parseInt(xy[3]);
			for(int x = x1; x < x2; x++) {  //此处不能取等号,否则会导致多算
				for(int y = y1; y < y2; y++) {  //此处同上
					if(!vis[x][y]) {
						vis[x][y] = true;
						sum++;
					}
				}
			}
		}
		out.println(sum);
		out.flush();
	}

}

法2:线段树+扫面线(有点难😢)

在这里插入图片描述

有点难

留坑

6.三体攻击(二分+前缀和+三维差分❗)

在这里插入图片描述

在这里插入图片描述
感觉看起来像差分,但是并不会,哈哈哈
没想到还有三维差分(✪ ω ✪)
具有单调性,二分答案判定是否可行

s(x,y,z)
b(x,y.x)
首先压缩掉z轴
s(x.y,z) = s(x - 1,y,z) + s(x,y-1,z)-s(x-1,y-1,z)+b(x,y,z)+s(x,y,z-1)-s(x-1,y,z-1)-s(x,y-1,z-1)+s(x-1,y-1,z-1)
在这里插入图片描述

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.Arrays;
public class Main 
{
    static final int N = 2000010;
    static long s[] = new long[N], b[] = new long[N],bp[] = new long[N];
    static int op[][] = new int[N / 2][7]; 
    static int d[][] = 
    {
        {0, 0, 0, 1},
        {0, 0, 1, -1},
        {0, 1, 0, -1},
        {0, 1, 1, 1},
        {1, 0, 0, -1},
        {1, 0, 1, 1},
        {1, 1, 0, 1},
        {1, 1, 1, -1},
    };
    static int A, B, C, m;

    public static void main(String[] args) throws Exception
    {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String str1[] = br.readLine().split(" ");
        A = Integer.parseInt(str1[0]);
        B = Integer.parseInt(str1[1]);
        C = Integer.parseInt(str1[2]);
        m = Integer.parseInt(str1[3]);
        String str2[] = br.readLine().split(" ");

        for(int i = 1; i <= A; i ++)
            for(int j = 1; j <= B; j ++)
                for(int k = 1; k <= C; k ++)
                    s[get(i, j, k)] = Long.parseLong(str2[get(i - 1, j - 1, k - 1)]);

        //构造差分矩阵b[]
        for(int i = 1; i <= A; i ++)
            for(int j = 1; j <= B; j ++)
                for(int k = 1; k <= C; k ++)
                    for(int u = 0; u < 8; u ++)
                    {
                        int x = i - d[u][0], y = j - d[u][1], z = k - d[u][2], t = d[u][3];
                        b[get(i, j, k)] += s[get(x, y, z)] * t;
                    }

        //操作
        for(int i = 1; i <= m; i ++)
        {
            String str3[] = br.readLine().split(" ");

            for(int j = 0; j < 7; j ++)
                op[i][j] = Integer.parseInt(str3[j]);
        }

        //二分攻击次数
       int l = 1, r = m;
		int res = -1;
		while(l <= r) {
			int mid = (l + r) >> 1;
		if(check(mid)) {
			res = mid;
			r = mid - 1;
		}else {
			l = mid + 1;
		}
		}

		System.out.println(res);
    }

    //三维坐标(i, j, k)映射成一维坐标(i * B + j) * C + k
    public static int get(int i, int j, int k)
    {
        return (i * B + j) * C + k;
    }

    //二分答案
    public static boolean check(int mid)
    {
        //拷贝一份
        for(int i = 0; i < N; i ++)
            bp[i] = b[i];

        for(int i = 1; i <= mid; i ++)
        {
            int x1 = op[i][0], x2 = op[i][1];
            int y1 = op[i][2], y2 = op[i][3];
            int z1 = op[i][4], z2 = op[i][5];
            int h = op[i][6];
            bp[get(    x1,      y1,      z1)] -= h;
            bp[get(    x1,      y1,  z2 + 1)] += h;
            bp[get(    x1,  y2 + 1,      z1)] += h;
            bp[get(    x1,  y2 + 1,  z2 + 1)] -= h;
            bp[get(x2 + 1,      y1,      z1)] += h;
            bp[get(x2 + 1,      y1,  z2 + 1)] -= h;
            bp[get(x2 + 1,  y2 + 1,      z1)] -= h;
            bp[get(x2 + 1,  y2 + 1,  z2 + 1)] += h;
        }

        Arrays.fill(s, 0);

        for(int i = 1; i <= A; i ++)
            for(int j = 1; j <= B; j ++)
                for(int k = 1; k <= C; k ++)
                {
                    s[get(i, j, k)] = bp[get(i, j, k)];

                    for(int u = 1; u < 8; u ++)
                    {
                        int x = i - d[u][0], y = j - d[u][1], z = k - d[u][2], t = d[u][3];
                        s[get(i, j, k)] -= s[get(x, y, z)] * t;
                    }

                    if(s[get(i, j, k)] < 0)
                        return true;
                }

        return false;
    }
}

7.螺旋折线(推规律)❗

在这里插入图片描述

在这里插入图片描述

看大佬的博客,真的很奇妙博客来源
找规律发现每层的右上角所需步数为4k*k,我们可以先通过坐标推出所在的层数,因为我们是顺时针转的,所以当点在
该层的左或上的时候,减去该点到右上角的曼哈顿距离,如果在该层的有或下加上该点到右上角的曼哈顿距离。
注意数据范围

public class Main{
	static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
	static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
	static int N = (int) (2e6 + 10);
	static int n = 0, m = 0;
	public static void main(String[] args) throws Exception{
		String[] xy = br.readLine().split(" ");
		long x = Long.parseLong(xy[0]);
		long y = Long.parseLong(xy[1]);
		long k = Math.max(Math.abs(x),Math.abs(y));
		long base = 4 * k * k;
		if(x >= y) {
			System.out.println(base + Math.abs(x - k) + Math.abs(y - k));
		}else {
			System.out.println(base - Math.abs(x - k) - Math.abs(y - k));
		}
	}
}

8.差分(差分)

在这里插入图片描述
在这里插入图片描述
差分模板


public class Main{
    static BufferedReader br = new BufferedReader(new InputStreamReader((System.in)));
    static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
    static int N = (int)(1e5 + 100);
    static int[] a = new int[N];
    static int[] d = new int[N];
    static int n = 0, m = 0;
    public static void main(String[] args) throws Exception{
        String[]nm = br.readLine().split(" ");
        n = Integer.parseInt(nm[0]);
        m = Integer.parseInt(nm[1]);
        String[] aa = br.readLine().split(" ");
        for(int i = 1;i <= n; i++){
            a[i] = Integer.parseInt(aa[i - 1]);
            d[i] = a[i] - a[i - 1];
        }
        for(int i = 1; i <= m; i++){
            String[] lrc = br.readLine().split(" ");
            int l = Integer.parseInt(lrc[0]);
            int r = Integer.parseInt(lrc[1]);
            int c = Integer.parseInt(lrc[2]);
            d[l]+=c; d[r+1]-=c;
        }
        for(int i = 1; i <= n; i++){
            a[i] = d[i] + a[i - 1];
        }
        for(int i = 1; i <= n; i++){
            System.out.print(a[i] + " ");
        }
    }
}

9.差分矩阵(二维差分)

在这里插入图片描述

在这里插入图片描述
画个图,类比一下一维差分就好啦

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
public class Main{
	static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
	static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
	static int N = (int) (1e3 + 10);
	static int[][] d = new int[N][N];
	static long [][] sum = new long[N][N];
	static int n = 0, m = 0;
	public static void main(String[] args) throws Exception{
		String[] nm = br.readLine().split(" ");
		n = Integer.parseInt(nm[0]);
		m = Integer.parseInt(nm[1]);
		int q = Integer.parseInt(nm[2]);
		for(int i = 1; i <= n; i++) {
			String[] aa = br.readLine().split(" ");
			for(int j = 1; j <= m; j++) {
				sum[i][j] = Integer.parseInt(aa[j - 1]);
				insert(i,j,i,j,sum[i][j]);
			}
		}
		for(int i = 1; i <= q; i++) {
			String[] ops = br.readLine().split(" ");
			int x1 = Integer.parseInt(ops[0]);
			int y1 = Integer.parseInt(ops[1]);
			int x2 = Integer.parseInt(ops[2]);
			int y2 = Integer.parseInt(ops[3]);
			long c = Long.parseLong(ops[4]);
			insert(x1, y1, x2, y2, c);
		}
		for(int i = 1; i <= n; i++) {
			for(int j = 1; j <= m; j++) {
				sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + d[i][j];
			}
		}
		
		for(int i = 1; i <= n; i++) {
			for(int j = 1; j <= m; j++) {
				out.print(sum[i][j] + " ");
			}
			out.println();
		}
		out.flush();
	}
	private static void insert(int x1, int y1, int x2, int y2, long c) {
		d[x1][y1] += c;
		d[x1][y2 +1] -= c;
		d[x2 + 1][y1] -= c;
		d[x2 + 1][y2 + 1] += c; 
		
	}
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

释怀°Believe

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值