hdu 1166 敵兵佈陣(線段樹)
阿新 • • 發佈:2019-02-07
C國的死對頭A國這段時間正在進行軍事演習,所以C國間諜頭子Derek和他手下Tidy又開始忙乎了。A國在海岸線沿直線佈置了N個工兵營地,Derek和Tidy的任務就是要監視這些工兵營地的活動情況。由於採取了某種先進的監測手段,所以每個工兵營地的人數C國都掌握的一清二楚,每個工兵營地的人數都有可能發生變動,可能增加或減少若干人手,但這些都逃不過C國的監視。
中央情報局要研究敵人究竟演習什麼戰術,所以Tidy要隨時向Derek彙報某一段連續的工兵營地一共有多少人,例如Derek問:“Tidy,馬上彙報第3個營地到第10個營地共有多少人!”Tidy就要馬上開始計算這一段的總人數並彙報。但敵兵營地的人數經常變動,而Derek每次詢問的段都不一樣,所以Tidy不得不每次都一個一個營地的去數,很快就精疲力盡了,Derek對Tidy的計算
Input
第一行一個整數T,表示有T組資料。
每組資料第一行一個正整數N(N<=50000),表示敵人有N個工兵營地,接下來有N個正整數,第i個正整數ai代表第i個工兵營地裡開始時有ai個人(1<=ai<=50)。
接下來每行有一條命令,命令有4種形式:
(1) Add i j,i和j為正整數,表示第i個營地增加j個人(j不超過30)
(2)Sub i j ,i和j為正整數,表示第i個營地減少j個人(j不超過30);
(3)Query i j ,i和j為正整數,i<=j,表示詢問第i到第j個營地的總人數;
(4)End 表示結束,這條命令在每組資料最後出現;
每組資料最多有40000條命令
Output
對第i組資料,首先輸出“Case i:”和回車,
對於每個Query詢問,輸出一個整數並回車,表示詢問的段中的總人數,這個數保持在int以內。
Sample Input
1 10 1 2 3 4 5 6 7 8 9 10 Query 1 3 Add 3 6 Query 2 7 Sub 10 2 Add 6 3 Query 3 10 End
Sample Output
Case 1: 6 33 59
這題如果直接暴力做修改複雜度o(1),但是查詢複雜度o(n);使用字首和解題查詢o(1)但是更改要o(n);涉及區間求和時我們可以考慮用線段樹解題,方便維護區間的和。
程式碼(兩種做法):
第一種
#include<stdio.h>
#include<cstring>
#include<algorithm>
using namespace std;
int a[50005];
int ans;
struct node //定義樹的節點,l區間左端點,r區間右端點,sum區間和
{
int l,r,sum;
}tree[131075];
void build(int l, int r, int rt) //建樹,不斷遞迴直到區間不能再二分
{
tree[rt].l=l;
tree[rt].r=r;
if(l==r)
{
tree[rt].sum=a[l];
return;
}
int m=(l+r)/2;
build(l,m,rt*2);
build(m+1,r,rt*2+1);
tree[rt].sum=tree[rt*2].sum+tree[rt*2+1].sum; //回溯求出區間的和
}
void update(int pos, int C, int rt) //更新pos位置的點
{
if(tree[rt].l==pos&&tree[rt].r==pos) //找到pos這個點加上c
{
tree[rt].sum+=C;
return;
}
int m=(tree[rt].l+tree[rt].r)/2;
if(pos<=m) update(pos,C,rt*2);
else update(pos,C,rt*2+1);
tree[rt].sum=tree[rt*2].sum+tree[rt*2+1].sum; //回溯過程中每個包含pos這個元素的區間都會加上c
}
int Query(int L,int R,int rt) //查詢區間[l,r]的和
{
int mid=(tree[rt].l+tree[rt].r)/2; //尋找區間中點
if(L==tree[rt].l&&R==tree[rt].r) //如果l和r剛好是rt這個區間的左右端點,將區間和加到ans上
{
ans+=tree[rt].sum;
}
else if(L>mid) //如果l大於區間中點,遞迴右子節點
{
Query(L,R,rt*2+1);
}
else if(R<=mid) //如果r小於區間中點,遞迴左子節點
{
Query(L,R,rt*2);
}
else //如果區間[l,r]在rt區間內,分開兩個區間[l,mid]和[mid+1,r]進行遞迴
{
Query(L,mid,rt*2);
Query(mid+1,R,rt*2+1);
}
}
int main()
{
int t,n,i,x,y,count;
char s[8];
count=1;
scanf("%d",&t);
while(t--)
{
printf("Case %d:\n",count++);
memset(a,0,sizeof(a)); //初始化
scanf("%d",&n);
for(i=1;i<=n;i++)
scanf("%d",&a[i]); //記錄每個值
build(1,n,1); //構造線段樹
while(1)
{
ans=0;
scanf("%s",&s);
if(s[0]=='E')
break;
if(s[0]=='A')
{
scanf("%d%d",&x,&y);
update(x,y,1);
}
if(s[0]=='S')
{
scanf("%d%d",&x,&y);
update(x,-y,1);
}
if(s[0]=='Q')
{
scanf("%d%d",&x,&y);
ans=Query(x,y,1);
printf("%d\n",ans);
}
}
}
return 0;
}
第二種
#include<stdio.h>
#include<cstring>
#include<algorithm>
using namespace std;
int a[50005];
int ans;
struct node //定義樹的節點,l區間左端點,r區間右端點,sum區間和
{
int l,r,sum;
}tree[131075];
void build(int l, int r, int rt) //建樹,不斷遞迴直到區間不能再二分
{
tree[rt].l=l;
tree[rt].r=r;
if(l==r)
{
tree[rt].sum=a[l];
return;
}
int m=(l+r)/2;
build(l,m,rt*2);
build(m+1,r,rt*2+1);
tree[rt].sum=tree[rt*2].sum+tree[rt*2+1].sum; //回溯求出區間的和
}
void update(int pos, int C, int rt) //更新pos位置的點
{
if(tree[rt].l==pos&&tree[rt].r==pos) //找到pos這個點加上c
{
tree[rt].sum+=C;
return;
}
int m=(tree[rt].l+tree[rt].r)/2;
if(pos<=m) update(pos,C,rt*2);
else update(pos,C,rt*2+1);
tree[rt].sum=tree[rt*2].sum+tree[rt*2+1].sum; //回溯過程中每個包含pos這個元素的區間都會加上c
}
int Query(int L, int R, int l, int r, int rt) //查詢區間[L,R]的和
{
if(L<=l&&R>=r) //如果區間[L,R]比rt區間大或相等,直接返回rt區間的和
return tree[rt].sum;
int res=0;
int m=(l+r)/2; //求rt區間中點
if(L<=m) res+=Query(L,R,l,m,rt*2); //相應遞迴左子節點或者右子節點
if(R>m) res+=Query(L,R,m+1,r,rt*2+1);
return res; //回溯累加得到最終的答案
}
int main()
{
int t,n,i,x,y,count;
char s[8];
count=1;
scanf("%d",&t);
while(t--)
{
printf("Case %d:\n",count++);
memset(a,0,sizeof(a)); //初始化
scanf("%d",&n);
for(i=1;i<=n;i++)
scanf("%d",&a[i]); //記錄每個值
build(1,n,1); //構造線段樹
while(1)
{
ans=0;
scanf("%s",&s);
if(s[0]=='E')
break;
if(s[0]=='A')
{
scanf("%d%d",&x,&y);
update(x,y,1);
}
if(s[0]=='S')
{
scanf("%d%d",&x,&y);
update(x,-y,1);
}
if(s[0]=='Q')
{
scanf("%d%d",&x,&y);
ans=Query(x,y,1,n,1);
printf("%d\n",ans);
}
}
}
return 0;
}
第一種做法Query函式修改查詢區間的左右端點,比較容易理解,但是容易考慮不全面而出錯;第二種Query函式修改rt區間,也就是不斷遞迴查詢線段樹的節點,不易出錯,但是程式碼也沒有那麼容易理解。