藍橋杯_演算法訓練_操作格子
阿新 • • 發佈:2019-02-10
問題描述
有n個格子,從左到右放成一排,編號為1-n。
共有m次操作,有3種操作型別:
1.修改一個格子的權值,
2.求連續一段格子權值和,
3.求連續一段格子的最大值。
對於每個2、3操作輸出你所求出的結果。
輸入格式
第一行2個整數n,m。
接下來一行n個整數表示n個格子的初始權值。
接下來m行,每行3個整數p,x,y,p表示操作型別,p=1時表示修改格子x的權值為y,p=2時表示求區間[x,y]內格子權值和,p=3時表示求區間[x,y]內格子最大的權值。
輸出格式
有若干行,行數等於p=2或3的操作總數。
每行1個整數,對應了每個p=2或3操作的結果。
樣例輸入
4 3
1 2 3 4
2 1 3
1 4 3
3 1 4
樣例輸出
6
3
資料規模與約定
對於20%的資料n <= 100,m <= 200。
對於50%的資料n <= 5000,m <= 5000。
對於100%的資料1 <= n <= 100000,m <= 100000,0 <= 格子權值 <= 10000。
import java.util.Scanner;
/**
* @author 翔
*
*/
public class Main {
private static int[] arr;//儲存所有的格子數
private static int[][] op;//存放操作
private static int gridNum;//格子總數
private static int opNum;//操作種數
private static Node[] tree;//線段樹
private static int[] father;//father[i]:表示第i個格子線上段樹中的位置
private static int tempMax;//用於求區間最大值
private static int tempSum;//用於求區間權值和
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
init();//初始化,包括輸入引數、構建線段樹
fun();
}
private static void fun(){
for(int i=0;i<opNum;i++){
int p=op[i][0];
int x=op[i][1];
int y=op[i][2];
switch(p){
case 1:arr[x]=y;update(father[x]);break;
case 2:tempSum=0;getSum(x,y,1);System.out.println(tempSum);break;
case 3:tempMax=Integer.MIN_VALUE;getMax(x,y,1);System.out.println(tempMax);break;
}
}
}
// index為區間的序號(對應的區間是最大範圍的那個區間,也是第一個圖最頂端的區間,一般初始是 1 )
private static void getSum(int x,int y,int index){
if(x==tree[index].left&&y==tree[index].right){
tempSum+=tree[index].sum;
return;
}
int leftIndex=index*2;//左子樹在tree中的位置
if(x<=tree[leftIndex].right){//所求的區間在左子樹中有涉及
if(y<=tree[leftIndex].right){//左子樹完全包含所求的區間,則查詢區間形態不變
getSum(x,y,leftIndex);
}else{//半包含於左區間,則查詢區間拆分,左端點不變,右端點變為左子樹的右區間端點
getSum(x,tree[leftIndex].right,leftIndex);
}
}
int rightIndex=leftIndex+1;//右子樹在tree中的位置
if(y>=tree[rightIndex].left){//所求的區間在右子樹有涉及
if(x>=tree[rightIndex].left){//右子樹完全包含所求的區間,則查詢區間形態不變
getSum(x,y,rightIndex);
}else{// 半包含於右區間,則查詢區間拆分,與上同理
getSum(tree[rightIndex].left,y,rightIndex);
}
}
}
// index為區間的序號(對應的區間是最大範圍的那個區間,也是第一個圖最頂端的區間,一般初始是 1)
private static void getMax(int x,int y,int index){
if(x==tree[index].left&&y==tree[index].right){
tempMax=tree[index].max>tempMax?tree[index].max:tempMax;
return;
}
int leftIndex=index*2;//左子樹在tree中的位置
if(x<=tree[leftIndex].right){//所求的區間在左子樹中有涉及
if(y<=tree[leftIndex].right){//左子樹完全包含所求的區間,則查詢區間形態不變
getMax(x,y,leftIndex);
}else{//半包含於左區間,則查詢區間拆分,左端點不變,右端點變為左子樹的右區間端點
getMax(x,tree[leftIndex].right,leftIndex);
}
}
int rightIndex=leftIndex+1;//右子樹在tree中的位置
if(y>=tree[rightIndex].left){//所求的區間在右子樹有涉及
if(x>=tree[rightIndex].left){//右子樹完全包含所求的區間,則查詢區間形態不變
getMax(x,y,rightIndex);
}else{// 半包含於左區間,則查詢區間拆分,與上同理
getMax(tree[rightIndex].left,y,rightIndex);
}
}
}
private static void update(int index){// 從下往上更新(注:這個點本身已經在函式外更新過了)
if(index==1)return;// 向上已經找到了祖先(整個線段樹的祖先結點 對應的下標為1)
if(tree[index].left==tree[index].right){//該節點為葉子節點
tree[index].max=arr[tree[index].left];
tree[index].sum=arr[tree[index].left];
}
int fatherIndex=index/2;//代表父節點在tree中的位置
tree[fatherIndex].max=tree[fatherIndex*2].max>tree[fatherIndex*2+1].max?tree[fatherIndex*2].max:tree[fatherIndex*2+1].max;
tree[fatherIndex].sum=tree[fatherIndex*2].sum+tree[fatherIndex*2+1].sum;
update(fatherIndex);
}
//初始化,包括輸入引數、構建線段樹
private static void init(){
Scanner sc=new Scanner(System.in);
gridNum=sc.nextInt();
opNum=sc.nextInt();
arr=new int[gridNum+1];
op=new int[opNum][3];
tree=new Node[2*gridNum];//線段樹
father=new int[gridNum+1];//father[i]:代表arr[i]在tree中的位置
for(int i=1;i<=gridNum;i++){
arr[i]=sc.nextInt();//初始化各格子數
}
for(int i=0;i<opNum;i++){
for(int j=0;j<3;j++){
op[i][j]=sc.nextInt();//輸入所有操作
}
}
buildTree(1,gridNum,1);//構造線段樹
//更新區間最大值、區間和
for(int i=1;i<=gridNum;i++){
update(father[i]);
}
sc.close();
}
//為區間[left,right]建立一個以index為祖先的線段樹,index為陣列下標
private static void buildTree(int left,int right,int index){
tree[index]=new Node();
tree[index].left=left;
tree[index].right=right;
if(left==right){// 當區間長度為 0 時,結束遞迴
father[left]=index;// 能知道某個點對應的序號,為了update()的時候從下往上一直到頂
return;
}
//該結點往 左孩子的方向 繼續建立線段樹,線段的劃分是二分思想,如果寫過二分查詢的話這裡很容易接受 ,這裡將 區間[left,right] 一分為二了
buildTree(left,(int)Math.floor(((left+right)/2.0)),index*2);
// 該結點往 右孩子的方向 繼續建立線段樹
buildTree((int)Math.floor(((left+right)/2.0))+1,right,index*2+1);
}
}
class Node{
int left;
int right;
int sum;
int max;
}