線段樹的單點更新+區間求和 hdu1166敵兵佈陣 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 結構體實現的代碼 #include<stdio.h> #include<string.h> #include<iostream> #include<algorithm> using namespace std; const int maxn=50005; int num[maxn]; char str[30]; int n; typedef struct node{ int left,right,data; node* lchild; node* rchild; node(){ left=right=data=0; } }tree; int sum; tree* create(int a,int b){ tree *r; r=(tree*)malloc(sizeof(tree)); r->left=a; r->right=b; if(a==b){ r->data=num[a]; r->lchild=r->rchild=NULL; } else{ int mid=(a+b)>>1; r->lchild=create(a,mid); r->rchild=create(mid+1,b); r->data=r->lchild->data+r->rchild->data; } return r; } void updata(tree *r,int a,int b){ if(r->left==a&&r->right==a){ r->data+=b; return; } int mid=(r->left+r->right)>>1; if(a<=mid) updata(r->lchild,a,b); else{ updata(r->rchild,a,b); } r->data+=b; } void find(tree *r,int a,int b){ if(r->left==a&&r->right==b){ sum+=r->data; return; } int mid=(r->left+r->right)>>1; if(b<=mid) find(r->lchild,a,b); else if(a>mid) find(r->rchild,a,b); else{ find(r->lchild,a,mid); find(r->rchild,mid+1,b); } } int main(){ int t; int cas=1; scanf("%d",&t); while(t--){ scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d",&num[i]); } tree *T; T=create(1,n); int a,b; printf("Case %d:\n",cas++); while(scanf("%s",str)!=EOF){ if(str[0]=='E') break; scanf("%d%d",&a,&b); if(str[0]=='A') updata(T,a,b); else if(str[0]=='S') updata(T,a,-b); else{ sum=0; find(T,a,b); printf("%d\n",sum); } } } return 0; } hdu1754單點更新+區間求最大值 在每一個測試的第一行,有兩個正整數 N 和 M ( 0<N<=200000,0<M<5000 ),分別表明學生的數目和操做的數目。 學生ID編號分別從1編到N。 第二行包含N個整數,表明這N個學生的初始成績,其中第i個數表明ID爲i的學生的成績。 接下來有M行。每一行有一個字符 C (只取'Q'或'U') ,和兩個正整數A,B。 當C爲'Q'的時候,表示這是一條詢問操做,它詢問ID從A到B(包括A,B)的學生當中,成績最高的是多少。 當C爲'U'的時候,表示這是一條更新操做,要求把ID爲A的學生的成績更改成B。 Output 對於每一次詢問操做,在一行裏面輸出最高成績。 Sample Input 5 6 1 2 3 4 5 Q 1 5 U 3 6 Q 3 4 Q 4 5 U 2 9 Q 1 5 Sample Output 5 6 5 9 代碼 數組實現 #include<stdio.h> #include<string.h> #include<iostream> #include<algorithm> using namespace std; const int Maxn=200005; struct node{ int Max,left,right; }tree[Maxn*3]; int val[Maxn]; int create(int root,int left,int right){ tree[root].left=left; tree[root].right=right; if(left==right) return tree[root].Max=val[left]; int a,b,mid=(left+right)>>1; a=create(root<<1,left,mid); b=create(root<<1|1,mid+1,right); return tree[root].Max=max(a,b); } int updata(int root,int pos,int val){ if(pos<tree[root].left||tree[root].right<pos) return tree[root].Max; if(tree[root].left==pos&&tree[root].right==pos) return tree[root].Max=val; int a,b; a=updata(root<<1,pos,val); b=updata(root<<1|1,pos,val); return tree[root].Max=max(a,b); } int cal(int root,int left,int right){ if(tree[root].left>right||tree[root].right<left) return 0; if(tree[root].left>=left&&tree[root].right<=right) return tree[root].Max; int a,b; a=cal(root<<1,left,right); b=cal(root<<1|1,left,right); return max(a,b); } int main(){ int n,q; while(scanf("%d%d",&n,&q)!=EOF){ for(int i=1;i<=n;i++) scanf("%d",&val[i]); int tmp_1=create(1,1,n); char c; getchar(); for(int i=1;i<=q;i++){ int a,b; scanf("%c %d %d",&c,&a,&b); getchar(); if(c=='Q'){ int ans=cal(1,a,b); printf("%d\n",ans); } else{ int tmp_2= updata(1,a,b); } } } return 0; } hdu1698區間更新+區間求和 在一款遊戲dota中,有一我的物叫帕吉,他有一個鉤子,鉤子的每一節多是不一樣的材質,有的是銅的,有的是銀的 ,有的是金的,每一節銅鉤子的價值是1,銀鉤子的價值是2,金鉤子的價值是3。帕吉如今想對鉤子作一些操做,而後 求出鉤子的總價值。 數據的第一行是測試數據組數。 對於每組測試數據,第一行是一個數字N,表明帕吉鉤子的節數, 也就是長度;第二行是一個數字Q,表明帕吉要對鉤子進行幾回操做,接下來Q行,每行有三個數字X、Y和Z,表明把鉤 子的第X節到第Y節修改爲第Z種材質(第一種:銅;第二種:銀;第三種:金)。 對於每組測試數據,輸出帕吉鉤子的價值。 1 10 2 1 5 2 5 9 3 代碼 #include<iostream> #include<stdio.h> #include<cstring> using namespace std; #define MAXSIZE 1000000 int val[MAXSIZE]; int add[100010<<2]; int sum[100010<<2]; struct node { int total; int left; int right; int mark; //延時標記 } tree[MAXSIZE*3]; //下面兩種create均可以,選擇一種就可 //int create(int root,int left,int right) //{ // add[root]=0; // sum[root]=1; // tree[root].left=left; // tree[root].right=right; // if(left==right) // return tree[root].total=val[left]; // int middle=(left+right)>>1; // return tree[root].total=create(root<<1,left,middle)+create(root<<1|1,middle+1,right); //} void create(int root,int left,int right) { add[root]=0; sum[root]=1;//初始值爲1,若是爲0,有些在更新中沒有更新到的節點值就爲0了; tree[root].left=left; tree[root].right=right; if(left==right) return ; int middle=(left+right)>>1; create(root<<1,left,middle); create(root<<1|1,middle+1,right); } // 參數:詢問區間左端點,詢問區間右端點,每一個位置須要增長的值,當前節點序號 void update(int L, int R, int x, int root) { if (L<=tree[root].left && tree[root].right<= R) { // 當前區間被包含,處理相應附加信息 add[root] = x; // 更新延遲標記 sum[root] = x * (tree[root].right-tree[root].left+1); return; // 暫時不用再向下遞歸 } int mid = (tree[root].left+tree[root].right)>>1; if (add[root]) // 延遲標記不爲0,說明有未完成的更新,更新之 { add[root<<1] = add[root]; add[root<<1|1] = add[root]; sum[root<<1] = add[root] * (mid-tree[root].left+1); sum[root<<1|1] = add[root] * (tree[root].right-mid); add[root] = 0; // 不要忘了去除延遲標記 } if (L <= mid) // 左子區間中包含有更新區間的部分,須要更新 update(L, R, x, root<<1); if (R > mid) // 右子區間中包含有更新區間的部分,須要更新 update(L, R, x, root<<1|1); sum[root] = sum[root<<1] + sum[root<<1|1];//從葉子節點向上更新 } int main() { int t; scanf("%d",&t); for(int i=1,n,q; i<=t; i++) { memset(sum,0,sizeof(sum)); memset(add,0,sizeof(add)); scanf("%d%d",&n,&q); for(int j=1; j<=n; j++) val[j]=1; create(1,1,n); for(int j=0,x,y,z; j<q; j++) { scanf("%d%d%d",&x,&y,&z); update(x,y,z,1); } printf("Case %d: The total value of the hook is %d.\n",i,sum[1]); } } 線段樹的區間合併&&求區間的最長公共子序列 假設有一列數{Ai}(1≤i≤n),支持以下兩種操做: U A B: 將第A個數變成值 B Q A B: 輸出區間[A,B]的最長連續遞增子序列的長度 區間的最大連續遞增子序列(下面統稱LCIS)的長度 las[i]??? i 號節點對應區間包含左端點最長的LCIS長度 ras[i]??? i 號節點對應區間包含右端點最長的LCIS長度 mov[i] 表示i節點(線段)對應的LCIS的長度 ///i表示的是節點,即線段樹節點 例如當前節點i 對應的區間的序列爲: 1 2 5 2 1 0 4 2 6 則 las[i] = 3, ras[i] = 2, 同時,mov[i] = 3 對於線段樹中某個節點 i ,它的 mov[i] 值應該是如下幾種狀況的最大值: (1)左兒子 ls 節點的 mov[ls] 值 (2) 右兒子 rs 節點的 mov[rs] 值 (3) 左右兒子區間合併以後獲得的LCIS值。 從剛剛的例子中咱們發現: 區間合併獲得的LCIS 必定跨越左右兒子兩個區間, 所以其必然通過當前節點對應區間中間的兩個數! 咱們能夠用上面的後兩個數組來表示出來,即: ///ras[ls] + las[rs] 用語言表達就是: ///從左兒子對應區間的右端點向左延伸的最大LCIS長度 加上 ///從右兒子對應區間的左端點向右延伸的最大LCIS長度 首先是建樹: ///建樹模版 int build(int root,int l,int r) { if(l==r) { scanf("%d",&a[l]); ras[root]=las[root]=1; mov[root]=1; return; } int mid=(l+r)>>1; build(root<<1,l,mid); build(root<<1|1,mid+1,r); pushup(l,mid,r,root); } void pushup(int l,int mid,int r,int root) { las[root]=las[root<<1]; ras[root]=ras[root<<1|1]; mov[root]=0; if(a[mid]<a[mid+1]) { mov[root]=ras[root<<1]+las[root<<1|1] if(las[root<<1]==mid-l+1) las[root]+=las[root<<1|1]; if(ras[root<<1|1]==r-mid) ras[root]+=ras[root<<1]; } mov[root]=max(mov[root],max(mov[root<<1],mov[root<<1|1])); } ///查詢操做 int query(int L, int R,///L,R爲要查詢的區間, int l, int r, int rt){ if (L<=l && r<=R) return mov[rt]; int mid=(l+r)>>1; int res=0; if (R <= mid) return query(L, R, l, mid, rt<<1); else if (L > mid) return query(L, R, mid+1, r, rt<<1|1); else { int t1 = 0, t2 = 0, t3 = 0; t1 = query(L, R, l, mid, rt<<1); t2 = query(L, R, mid+1, r, rt<<1|1); if (va[mid] < va[mid+1]) // 注意約束條件 t3 = min(ras[rt<<1],mid-L+1) + min(las[rt<<1|1],R-mid); return max(t3,max(t1,t2)); } return res; } 還有一個操做: (1) U A B: 將第A個樹變成值 B 這個是以前單點更新操做的實現相似 ,惟一須要注意的地方就是維護節點信息的時候用到了和建樹相同的pushup() 操做 線段樹的掃描線問題 輸入就是若干個矩形的座標,求這些矩形的面積並 #include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<string> #include<queue> #include<algorithm> #include<map> #include<iomanip> #define INF 99999999 using namespace std; const int MAX=200+10; int mark[MAX<<2];//記錄某個區間的下底邊比上底邊多的個數 double sum[MAX<<2];//記錄某個區間下底邊比上底邊多的個數總長度 double Hash[MAX];//對橫座標x離散化 struct seg //線段 { double l,r,h; int d; seg() {} seg(double x1,double x2,double H,int D):l(x1),r(x2),h(H),d(D) {}//構造函數 bool operator<(const seg &a)const//自定義結構體排序 { return h<a.h; } } s[MAX]; void Upfather(int n,int left,int right){ if(mark[n]) sum[n]=Hash[right+1]-Hash[left];//表示該區間整個線段長度可做爲底邊 else if(left == right) sum[n]=0;//葉子結點區間長度爲0,則底邊長度爲0 else sum[n]=sum[n<<1]+sum[n<<1|1]; } void Update(int L,int R,int d,int n,int left,int right){ if(L<=left && right<=R) //該區間是當前掃描線段的一部分 { mark[n]+=d;//更新上下底邊之差 Upfather(n,left,right);//更新可用底邊長 return; } int mid=left+right>>1; if(L<=mid) Update(L,R,d,n<<1,left,mid); if(R>mid) Update(L,R,d,n<<1|1,mid+1,right); Upfather(n,left,right); } int search(double key,double *x,int n){//Search函數是爲了找到x座標爲key時,hash數組的下標 int left=0,right=n-1; while(left<=right){ int mid=(left+right)>>1; if(x[mid] == key) return mid; else if(x[mid]>key) right=mid-1; else left=mid+1; } return -1; } int main(){ double x1=0,y1=0,x2=0,y2=0; while(x1 != -2){ int size=0; while(cin>>x1>>y1>>x2>>y2,x1>=0){ if(x1>x2)swap(x1,x2); if(y1>y2)swap(y1,y2); Hash[size]=x1; s[size++]=seg(x1,x2,y1,1); Hash[size]=x2; s[size++]=seg(x1,x2,y2,-1); } sort(Hash,Hash+size);//把x座標從小到大排序 sort(s,s+size);//把線段按高度h從小到大排序 int k=1; for(int i=1; i<size; ++i) //去重複端點 { if(Hash[i] != Hash[i-1]) Hash[k++]=Hash[i]; } double ans=0; for(int i=0; i<size; ++i){ int L=search(s[i].l,Hash,k); int R=search(s[i].r,Hash,k)-1; Update(L,R,s[i].d,1,0,k-1);//掃描線段更新可用底邊長 ans+=sum[1]*(s[i+1].h-s[i].h);//新增長面積 } cout<<ans<<endl; } return 0; }