代码都用了Java 快速输出,输入。
一、单点修改+区间查询
题目描述(原题链接P3374 【模板】树状数组 1 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))
如题,已知一个数列,你需要进行下面两种操作:
-
将某一个数加上 xx
-
求出某区间每一个数的和
输入格式
第一行包含两个正整数 n,mn,m,分别表示该数列数字的个数和操作的总个数。
第二行包含 nn 个用空格分隔的整数,其中第 ii 个数字表示数列第 ii 项的初始值。
接下来 mm 行每行包含 33 个整数,表示一个操作,具体如下:
-
1 x k
含义:将第 xx 个数加上 kk -
2 x y
含义:输出区间 [x,y][x,y] 内每个数的和
输出格式
输出包含若干行整数,即为所有操作 22 的结果。
输入输出样例
输入 #1
5 5 1 5 4 2 3 1 1 3 2 2 5 1 3 -1 1 4 2 2 1 4
输出 #1
14 16
虽然标题是树状数组,但是可以拿来练习线段树(能用树状数组做的都能用线段树做出来的),很简单,就是单点修改+区间查询
import java.util.*;
import java.io.*;
public class Main {
private static StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
private static PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
private static BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
private static String Line() {
String a = "";
try {
a = bf.readLine();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
return a;
}
private static int Int() {
try {
st.nextToken();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
return (int)st.nval;
}
private static void build(int p,int l,int r) {
if(l == r) {
tree[p] = num[l];
return ;
}
int mid = (l+r)/2;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
tree[p] = tree[p*2] + tree[p*2+1];
}
private static void updateone(int p,int l,int r,int pi,int add) {
if( l == pi && r == pi) {
num[pi]+=add;
tree[p] = num[pi];
return;
}
int mid = (l+r)/2;
if(mid >= pi) {
updateone(p*2,l,mid,pi,add);
}
else {
updateone(p*2+1,mid+1,r,pi,add);
}
tree[p] = tree[p*2]+tree[p*2+1];
}
private static int query(int p,int l,int r,int x,int y) {
if(x<= l && r <= y) {
return tree[p];
}
int mid = (l+r)/2;
int ans = 0;
if(x <= mid) {
ans+=query(p*2,l,mid,x,y);
}
if(mid < y) {
ans+=query(p*2+1,mid+1,r,x,y);
}
return ans;
}
private static int n,m,num[],tree[];
public static void main(String[] args) {
// TODO 自动生成的方法存根
n = Int();
m = Int();
num = new int[n+1];
tree = new int[n*4];
for(int i = 1;i<=n;i++) {
num[i] = Int();
}
build(1,1,n);
for(int i = 0;i<m;i++) {
int t = Int();
int x = Int();
int y = Int();
if(t == 1) {
updateone(1,1,n,x,y);
}
else {
pw.println(query(1,1,n,x,y));
}
}
pw.flush();
}
}
二、 区间修改与区间查询
题目描述(原题链接P3372 【模板】线段树 1 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))
如题,已知一个数列,你需要进行下面两种操作:
- 将某区间每一个数加上 kk。
- 求出某区间每一个数的和。
输入格式
第一行包含两个整数 n, mn,m,分别表示该数列数字的个数和操作的总个数。
第二行包含 nn 个用空格分隔的整数,其中第 ii 个数字表示数列第 ii 项的初始值。
接下来 mm 行每行包含 33 或 44 个整数,表示一个操作,具体如下:
1 x y k
:将区间 [x, y][x,y] 内每个数加上 kk。2 x y
:输出区间 [x, y][x,y] 内每个数的和。
输出格式
输出包含若干行整数,即为所有操作 2 的结果。
输入输出样例
输入 #1
5 5 1 5 4 2 3 2 2 4 1 2 3 2 2 3 4 1 1 5 1 2 1 4
输出 #1
11 8 20
区间修改+查询(因为Java同时输入数字和字母的话容易出错,所以直接String类型快输后转化为int类型)
import java.util.*;
import java.io.*;
public class Main{
private static StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
private static PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
private static BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
private static class StopMsgException extends Exception{
}
private static int Int() {
try {
st.nextToken();
}
catch(IOException e) {
e.printStackTrace();
}
return (int)st.nval;
}
private static double Dou() {
try {
st.nextToken();
}
catch (IOException e) {
e.printStackTrace();
}
return st.nval;
}
private static String Str() {
try {
st.nextToken();
}
catch(IOException e) {
e.printStackTrace();
}
return st.sval;
}
private static String Line() {
String p = "";
try {
p = bf.readLine();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
return p;
}
private static void build(int node,int l,int r) {
if(l == r) {
tree[node] = mp[l];
return ;
}
int mid = (l+r)/2;
build(node*2,l,mid);
build(node*2+1,mid+1,r);
tree[node] = tree[node*2]+tree[node*2+1];
}
private static void push(int node,int l,int r) {
if(lazy[node]>0) {
lazy[node*2]+=lazy[node];
lazy[node*2+1]+=lazy[node];
int mid = (l+r)/2;
tree[node*2]+=(mid-l+1)*lazy[node];
tree[node*2+1]+=(r-mid)*lazy[node];
lazy[node] = 0;
}
}
private static void update(int node,int l,int r,int nl,int nr,long add) {
if(nl <= l && nr >= r) {
lazy[node]+=add;
tree[node]+=(r-l+1)*add;
return;
}
push(node,l,r);
int mid = (l+r)/2;
if(mid >= nl) {
update(node*2,l,mid,nl,nr,add);
}
if(mid < nr) {
update(node*2+1,mid+1,r,nl,nr,add);
}
tree[node] = tree[node*2]+tree[node*2+1];
}
private static long find(int node,int l,int r,int nl,int nr) {
if(nl <= l && nr >= r) {
return tree[node];
}
push(node,l,r);
int mid = (l+r)/2;
long sum = 0;
if(mid >= nl) {
sum+=find(node*2,l,mid,nl,nr);
}
if(mid < nr) {
sum+=find(node*2+1,mid+1,r,nl,nr);
}
return sum;
}
private static int n,m;
private static long mp[],tree[],lazy[];
public static void main(String[] args) {
// TODO 自动生成的方法存根
String p = Line();
String a[] = p.split(" ");
n = Integer.parseInt(a[0]);
m = Integer.parseInt(a[1]);
p = Line();
a = p.split(" ");
mp = new long[n+1];
for(int i = 0;i<a.length;i++) {
mp[i+1] = Long.parseLong(a[i]);
}
tree = new long[n*4];
lazy = new long[n*4];
build(1,1,n);
for(int i = 0;i<m;i++) {
p = Line();
a = p.split(" ");
if(a[0].equals("1")) {
int x = Integer.parseInt(a[1]);
int y = Integer.parseInt(a[2]);
long k = Long.parseLong(a[3]);
update(1,1,n,x,y,k);
}
else {
int x = Integer.parseInt(a[1]);
int y = Integer.parseInt(a[2]);
pw.println(find(1,1,n,x,y));
}
}
pw.flush();
}
}
三、区间查找最大、最小值
Balanced Lineup(原题链接:3264 -- Balanced Lineup (poj.org))
Time Limit: 5000MS | Memory Limit: 65536K | |
Total Submissions: 92640 | Accepted: 41154 | |
Case Time Limit: 2000MS |
Description
For the daily milking, Farmer John's N cows (1 ≤ N ≤ 50,000) always line up in the same order. One day Farmer John decides to organize a game of Ultimate Frisbee with some of the cows. To keep things simple, he will take a contiguous range of cows from the milking lineup to play the game. However, for all the cows to have fun they should not differ too much in height.
Farmer John has made a list of Q (1 ≤ Q ≤ 200,000) potential groups of cows and their heights (1 ≤ height ≤ 1,000,000). For each group, he wants your help to determine the difference in height between the shortest and the tallest cow in the group.
Input
Line 1: Two space-separated integers, N and Q.
Lines 2..N+1: Line i+1 contains a single integer that is the height of cow i
Lines N+2..N+Q+1: Two integers A and B (1 ≤ A ≤ B ≤ N), representing the range of cows from A to B inclusive.
Output
Lines 1..Q: Each line contains a single integer that is a response to a reply and indicates the difference in height between the tallest and shortest cow in the range.
Sample Input
6 3 1 7 3 4 2 5 1 5 4 6 2 2
Sample Output
6 3 0
Source
题解:就是找区间最大值与区间最小值的差就OK
import java.util.*;
import java.io.*;
public class Main {
private static StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
private static PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
private static BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
private static String Line() {
String a = "";
try {
a = bf.readLine();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
return a;
}
private static int Int() {
try {
st.nextToken();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
return (int)st.nval;
}
private static void build(int p,int l,int r) {
if(l == r) {
tree[p] = maxn[p] = minn[p] = num[l];
return ;
}
int mid = (l+r)/2;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
tree[p] = tree[p*2] + tree[p*2+1];
maxn[p] = Math.max(maxn[p*2], maxn[p*2+1]);
minn[p] = Math.min(minn[p*2], minn[p*2+1]);
}
private static int findmax(int p,int l,int r,int x,int y) {
if(x <= l && r <= y) {
return maxn[p];
}
int mid = (l+r)/2;
int maxx = Integer.MIN_VALUE;
if(mid >= x) {
maxx = Math.max(maxx, findmax(p*2,l,mid,x,y));
}
if(mid < y) {
maxx = Math.max(maxx,findmax(p*2+1,mid+1,r,x,y));
}
return maxx;
}
private static int findmin(int p,int l,int r,int x,int y) {
if(x <= l && r <= y) {
return minn[p];
}
int mid = (l+r)/2;
int minn = Integer.MAX_VALUE;
if(mid >= x) {
minn = Math.min(minn, findmin(p*2,l,mid,x,y));
}
if(mid < y) {
minn = Math.min(minn,findmin(p*2+1,mid+1,r,x,y));
}
return minn;
}
private static int n,m,num[],tree[],maxn[],minn[];
public static void main(String[] args) {
// TODO 自动生成的方法存根
n = Int();
m = Int();
num = new int[n+1];
tree = new int[n*4];
maxn = new int[n*4];
minn = new int[n*4];
for(int i = 1;i<=n;i++) {
num[i] = Int();
}
build(1,1,n);
for(int i = 0;i<m;i++) {
int x = Int();
int y = Int();
pw.println(findmax(1,1,n,x,y) - findmin(1,1,n,x,y));
}
pw.flush();
}
}
四、区间加法、乘法更新+区间查询
题目描述(题目链接P3373 【模板】线段树 2 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))
如题,已知一个数列,你需要进行下面三种操作:
-
将某区间每一个数乘上 xx
-
将某区间每一个数加上 xx
-
求出某区间每一个数的和
输入格式
第一行包含三个整数 n,m,pn,m,p,分别表示该数列数字的个数、操作的总个数和模数。
第二行包含 nn 个用空格分隔的整数,其中第 ii 个数字表示数列第 ii 项的初始值。
接下来 mm 行每行包含若干个整数,表示一个操作,具体如下:
操作 11: 格式:1 x y k
含义:将区间 [x,y][x,y] 内每个数乘上 kk
操作 22: 格式:2 x y k
含义:将区间 [x,y][x,y] 内每个数加上 kk
操作 33: 格式:3 x y
含义:输出区间 [x,y][x,y] 内每个数的和对 pp 取模所得的结果
输出格式
输出包含若干行整数,即为所有操作 33 的结果。
输入输出样例
输入 #1
5 5 38 1 5 4 2 3 2 1 4 1 3 2 5 1 2 4 2 2 3 5 5 3 1 4
输出 #1
17 2
题解:多准备一个懒惰数组保存乘法的操作
import java.util.*;
import java.io.*;
public class Main {
private static StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
private static PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
private static BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
private static String Line() {
String a = "";
try {
a = bf.readLine();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
return a;
}
private static int Int() {
try {
st.nextToken();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
return (int)st.nval;
}
private static void build(int l,int r,int p) {
la1[p] = 1;
if(l == r) {
tree[p] = mp[l];
return ;
}
int mid = (l+r)/2;
build(l,mid,p*2);
build(mid+1,r,p*2+1);
tree[p] = (tree[p*2]+tree[p*2+1])%mod;
}
private static void push(int p,int x,int y) {
if(x == y) {
return ;
}
int mid = (x+y)/2;
tree[p*2] = (tree[p*2]*la1[p] + la2[p]*(mid-x+1)) % mod;
tree[p*2+1] = (tree[p*2+1]*la1[p] + la2[p]*(y - mid)) % mod;
la1[p*2] = (la1[p*2] * la1[p]) % mod;
la1[p*2+1] = (la1[p*2+1] * la1[p]) % mod;
la2[p*2] = (la2[p*2]*la1[p] + la2[p]) % mod;
la2[p*2+1] = (la2[p*2+1]*la1[p] + la2[p]) % mod;
la1[p] = 1;
la2[p] = 0;
}
private static void update1(int x,int y,int d,int p,int bigl,int bigr) {
if(x<=bigl && bigr <= y) {
tree[p] = ((tree[p]%mod)*d)%mod;
la1[p] = ((la1[p]%mod)*d)%mod;
la2[p] = ((la2[p]%mod)*d)%mod;
return ;
}
int mid = (bigl+bigr)/2;
push(p,bigl,bigr);
if(x <= mid) {
update1(x,y,d,p*2,bigl,mid);
}
if(y > mid) {
update1(x,y,d,p*2+1,mid+1,bigr);
}
tree[p] = (tree[p*2]+tree[p*2+1])%mod;
}
private static void update2(int x,int y,int d,int p,int bigl,int bigr) {
if(x<=bigl && bigr <= y) {
tree[p] = (tree[p] + d*(bigr - bigl + 1)) % mod;
la2[p] = (la2[p] + d) % mod;
return ;
}
int mid = (bigl + bigr)/2;
push(p,bigl,bigr);
if(x<= mid) {
update2(x,y,d,p*2,bigl,mid);
}
if(y > mid) {
update2(x,y,d,p*2+1,mid+1,bigr);
}
tree[p] = (tree[p*2]+tree[p*2+1])%mod;
}
private static long query(int x,int y,int p,int bigl,int bigr) {
if(x <= bigl && bigr <= y) {
return tree[p]%mod;
}
int mid = (bigl + bigr)/2;
push(p,bigl,bigr);
long ans = 0;
if(x<= mid) {
ans+=query(x,y,p*2,bigl,mid);
}
if(mid < y) {
ans+=query(x,y,p*2+1,mid+1,bigr);
}
return ans % mod;
}
private static int n,m,mod,mp[];
private static long tree[],la1[],la2[];
public static void main(String[] args) {
// TODO 自动生成的方法存根
n = Int();
m = Int();
mod = Int();
mp = new int[n+1];
tree = new long[4*n];
la1 = new long[4*n];
la2 = new long[4*n];
for(int i = 1;i<=n;i++) {
mp[i] = Int();
}
build(1,n,1);
for(int i = 0;i<m;i++) {
int t = Int();
int x = Int();
int y = Int();
if(t == 1) {
int k = Int();
update1(x,y,k,1,1,n);
}
else if(t == 2) {
int k = Int();
update2(x,y,k,1,1,n);
}
else {
pw.println(query(x,y,1,1,n));
}
}
pw.flush();
}
}
五、实际应用题:
题目描述(P4588 [TJOI2018]数学计算 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))
小豆现在有一个数 xx,初始值为 11。小豆有 QQ 次操作,操作有两种类型:
1 m
:将 xx 变为 x \times mx×m,并输出 x \bmod MxmodM
2 pos
:将 xx 变为 xx 除以第 pospos 次操作所乘的数(保证第 pospos 次操作一定为类型 1,对于每一个类型 1 的操作至多会被除一次),并输出 x \bmod MxmodM。
输入格式
一共有 tt 组输入。
对于每一组输入,第一行是两个数字 Q,MQ,M。
接下来 QQ 行,每一行为操作类型 opop,操作编号或所乘的数字 mm(保证所有的输入都是合法的)。
输出格式
对于每一个操作,输出一行,包含操作执行后的 x \bmod MxmodM 的值。
输入输出样例
输入 #1
1 10 1000000000 1 2 2 1 1 2 1 10 2 3 2 4 1 6 1 7 1 12 2 7
输出 #1
2 1 2 20 10 1 6 42 504 84
首先做这个题时,以为可以直接模拟,O(n)。但是后面发现,它好像x不用mod,只是输出的时候mod.所以可能需要到高精度,于是用上了大数:
import java.util.*;
import java.io.*;
import java.math.BigInteger;
public class Main {
private static StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
private static PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
private static BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
private static String Line() {
String a = "";
try {
a = bf.readLine();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
return a;
}
private static int Int() {
try {
st.nextToken();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
return (int)st.nval;
}
private static long Lon() {
try {
st.nextToken();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
return (long)st.nval;
}
private static int t,n;
private static long mod;
public static void main(String[] args) {
// TODO 自动生成的方法存根
t = Int();
while(t-- !=0) {
BigInteger x = BigInteger.ONE;
n = Int();
mod = Lon();
long mp[] = new long[n+1];
for(int i = 1;i<=n;i++) {
int m = Int();
if(m == 1) {
mp[i] = Lon();
x = x.multiply(BigInteger.valueOf(mp[i]));
pw.println(x.mod(BigInteger.valueOf(mod)));
}
else {
int pi = Int();
x = x.divide(BigInteger.valueOf(mp[pi]));
pw.println((x.mod(BigInteger.valueOf(mod))));
}
}
}
pw.flush();
}
}
果不其然,只有22分,其他的TLE了,呜呜呜。(果然蓝题不可能这么简单就出来)
于是换了方法,用线段树,让时间作为下标,数组初始化为1,查询结果不就是数组中每个数的乘积嘛,也就是tree[1]!!!!!每次操作1的时候,对应的地方就改为m,操作2的时候,将对应的地方变为1(因为这个地方起那面肯定被改过的,相当于前面乘以10,后面要除它,直接把乘10 换成乘1就是同样的效果!!!!)然后修改完输出tree[1],就是结果了,(记得mod一下哦)上AC代码。
import java.util.*;
import java.io.*;
import java.math.BigInteger;
public class Main {
private static StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
private static PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
private static BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
private static String Line() {
String a = "";
try {
a = bf.readLine();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
return a;
}
private static int Int() {
try {
st.nextToken();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
return (int)st.nval;
}
private static long Lon() {
try {
st.nextToken();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
return (long)st.nval;
}
private static void build(int p,int l,int r) {
if(l == r) {
tree[p] = num[l];
return;
}
int mid = (l+r)/2;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
tree[p] = (tree[p*2]%mod*tree[p*2+1]%mod)%mod;
}
private static void update1(int p,int l,int r,int pi,long mut) {//乘法
if( l == pi && r == pi) {
tree[p] = mut;
return ;
}
int mid = (l+r)/2;
if(mid >= pi) {
update1(p*2,l,mid,pi,mut);
}
else {
update1(p*2+1,mid+1,r,pi,mut);
}
tree[p] = (tree[p*2]%mod*tree[p*2+1]%mod)%mod;
}
private static void update2(int p,int l,int r,int pi) {//除法,也就是将数字换为1
if( l == pi && r == pi) {
tree[p] = 1;
return ;
}
int mid = (l+r)/2;
if(mid >= pi) {
update2(p*2,l,mid,pi);
}
else {
update2(p*2+1,mid+1,r,pi);
}
tree[p] = (tree[p*2]%mod*tree[p*2+1]%mod)%mod;
}
private static int t,n,num[];
private static long mod,tree[];
public static void main(String[] args) {
// TODO 自动生成的方法存根
t = Int();
while(t--!=0) {
n = Int();
mod = Int();
tree = new long[n*4];
num = new int[n+1];
Arrays.fill(num, 1);
build(1,1,n);
for(int i = 1;i<=n;i++) {
int t = Int();
if(t == 1) {
long m = Lon();
update1(1,1,n,i,m);
pw.println(tree[1]);//直接输出tree[1]就好,无需查询
}
else {
int pi = Int();
update2(1,1,n,pi);
pw.println(tree[1]);//直接输出tree[1]就好,无需查询
}
}
}
pw.flush();
}
}