(一)線段樹node
1.E - Lost Cowsios
Input算法
Output數組
Sample Inputide
5 1 2 1 0
Sample Outputpost
2 4 5 3 1
解題思路:題意是已知每一個數字前面比她小的數的個數組成pre[]數組,讓咱們求他們的ans[]順序。
咱們能夠用暴力的方法來解決這道題:
思路是從後往前處理pre數組,例如pre[]:0 1 2 1 0
pre[5]=0,表示ans[5]前面比她小的有0個,即他是最小的,所以ans[5]=1;同時ans數組中將再也不包括1
pre[4]=1,表示ans[4]前面比他小的有1個,即他是第二小,所以ans[4]=3;同時ans數組不在包括3
。。。
每次都是從剩下的編號裏找第pre[i]+1小,就是ans[i]
咱們解題用到三個數組,pre,ans,num[]數組記錄被處理過的數字,處理過就記爲-1,設置num的初始值就爲下標值
代碼:
#include<iostream> #include<cstdio> using namespace std; const int N=8010; int pre[N],ans[N],num[N];//數組的第0個都不用,從1開始 int main() { int n,i,j; scanf("%d",&n); pre[1]=0; for(i=2;i<=n;i++) scanf("%d",&pre[i]); for(i=1;i<=n;i++) num[i]=i;//給num賦初值 for(i=n;i>=1;i--) { int k=0; for(j=1;j<=n;j++) { if(num[j]!=-1) { k++; if(k==pre[i]+1)//找到第pre[i]+1小的數 { ans[i]=num[j];//用ans記錄 num[j]=-1;//num數組標記 break; } } } } for(i=1;i<=n;i++) printf("%d\n",ans[i]); return 0; }
用暴力若是數量級較大就不太適合了會TLE,那麼就引出咱們的線段樹測試
咱們用線斷樹實現的是建樹與修改和查詢。ui
代碼:this
#include<iostream> #include<cstdio> using namespace std; const int N=10000; struct{ int l,r,len;//len儲存這個區間的數字個數 }tree[4*N]; int pre[N],ans[N]; void BuildTree(int left,int right,int u)//建樹 { tree[u].l=left; tree[u].r=right; tree[u].len=right-left+1; if(left==right) { return; } BuildTree(left,(left+right)>>1,u<<1);//遞歸左子樹 BuildTree(((left+right)>>1)+1,right,(u<<1)+1);//遞歸右子樹 } int query(int u,int num)//維護+查詢 { tree[u].len--;//該區間長度-1 if(tree[u].l==tree[u].r) return tree[u].l;//找到該點返回他的下標 if(tree[u<<1].len<num)//若是左子樹的各數不夠,就查詢右子樹起第num-tree[u<<1].len的數下標 return query((u<<1)+1,num-tree[u<<1].len); if(tree[u<<1].len>=num)//左子樹個數足夠,就找第num個數 return query(u<<1,num); } int main() { int n,i; scanf("%d",&n); pre[1]=0; for(i=2;i<=n;i++) scanf("%d",&pre[i]); BuildTree(1,n,1);//先建樹 for(i=n;i>=1;i--)//從後往前找 ans[i]=query(1,pre[i]+1); for(i=1;i<=n;i++) printf("%d\n",ans[i]); return 0; }
2.G - A Simple Problem with Integersspa
You have N integers, A1, A2, ... , AN. You need to deal with two kinds of operations. One type of operation is to add some given number to each number in a given interval. The other is to ask for the sum of numbers in a given interval.
Input
The first line contains two numbers N and Q. 1 ≤ N,Q ≤ 100000.
The second line contains N numbers, the initial values of A1, A2, ... , AN. -1000000000 ≤ Ai ≤ 1000000000.
Each of the next Q lines represents an operation.
"C a b c" means adding c to each of Aa, Aa+1, ... , Ab. -10000 ≤ c ≤ 10000.
"Q a b" means querying the sum of Aa, Aa+1, ... , Ab.
Output
You need to answer all Q commands in order. One answer in a line.
Sample Input
10 5 1 2 3 4 5 6 7 8 9 10 Q 4 4 Q 1 10 Q 2 4 C 3 6 3 Q 2 4
Sample Output
4 55 9 15
解題思路:區間修改與求和(模板題)
#include<iostream> #include<cstdio> using namespace std; const int N=1e5+10; long long sum[N<<2],add[N<<2]; void push_up(int rt) { sum[rt]=sum[rt<<1]+sum[rt<<1|1]; } //更新rt的子節點 void push_down(int rt,int m) { if(add[rt]) { add[rt<<1]+=add[rt]; add[rt<<1|1]+=add[rt]; sum[rt<<1]+=(m-(m>>1))*add[rt]; sum[rt<<1|1]+=(m>>1)*add[rt]; add[rt]=0;//取消標記 } } #define lson l,mid,rt<<1 #define rson mid+1,r,rt<<1|1 void build(int l,int r,int rt) { add[rt]=0; if(l==r) { scanf("%lld",&sum[rt]); return; } int mid=(r+l)>>1; build(lson); build(rson); push_up(rt); } void update(int a,int b,long long c,int l,int r,int rt) { if(a<=l&&b>=r) { sum[rt]+=(r-l+1)*c; add[rt]+=c; return; } push_down(rt,r-l+1); int mid=(l+r)>>1; if(a<=mid) update(a,b,c,lson); if(b>mid) update(a,b,c,rson); push_up(rt);//更新區間和 } long long query(int a,int b,int l,int r,int rt)//區間求和 { if(a<=l&&b>=r) return sum[rt]; push_down(rt,r-l+1); int mid=(l+r)>>1; long long ans=0; if(a<=mid) ans+=query(a,b,lson); if(b>mid) ans+=query(a,b,rson); return ans; } int main() { int n,m; scanf("%d %d",&n,&m); build(1,n,1); while(m--) { char str[2]; int a,b; long long c; scanf("%s",str); if(str[0]=='C') { scanf("%d%d%lld",&a,&b,&c); update(a,b,c,1,n,1); } else { scanf("%d%d",&a,&b); printf("%lld\n",query(a,b,1,n,1)); } } return 0; }
3.H - 敵兵佈陣
每組數據第一行一個正整數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<iostream> #include<cstdio> using namespace std; const int N=100010; int t,n,w[N]; struct node { int l,r; int sum; }tr[N*4]; void pushup(int u) { tr[u].sum=tr[u*2].sum+tr[u*2+1].sum; } void build(int u,int l,int r) { if(l==r) tr[u]={l,r,w[r]}; else { tr[u]={l,r}; int mid=l+r>>1; build(u*2,l,mid),build(u*2+1,mid+1,r); pushup(u); } } int query(int u,int l,int r) { if(tr[u].l>=l&&tr[u].r<=r) return tr[u].sum; int mid=tr[u].l+tr[u].r>>1; int sum=0; if(l<=mid) sum=query(u*2,l,r); if(r>mid) sum+=query(u*2+1,l,r); return sum; } void modify(int u,int x,int v) { if(tr[u].l==tr[u].r) tr[u].sum+=v; else { int mid=(tr[u].l+tr[u].r)>>1; if(x<=mid) modify(u<<1,x,v); else modify(u*2+1,x,v); pushup(u); } } int main() { int i,j; scanf("%d",&t); for(j=1;j<=t;j++) { scanf("%d",&n); for(i=1;i<=n;i++) { scanf("%d",&w[i]); } char str[10]; int a,b; build(1,1,n); printf("Case %d:\n",j); while(scanf("%s",str)&&str[0]!='E') { scanf("%d%d",&a,&b); if(str[0]=='Q') printf("%d\n",query(1,a,b)); else if(str[0]=='A') modify(1,a,b); else if(str[0]=='S') modify(1,a,-b); } } return 0; }
4.I - Minimum Inversion Number
InputThe input consists of a number of test cases. Each case consists of two lines: the first line contains a positive integer n (n <= 5000); the next line contains a permutation of the n integers from 0 to n-1.
OutputFor each case, output the minimum inversion number on a single line.
Sample Input
10 1 3 6 9 0 8 5 7 4 2
Sample Output
16
解題思路:題意大概是求逆序數的最小值,有n個序列求他們的逆序數的最小值,這n個序列是經過不斷使第一個數放到末尾構成的
咱們能夠從中發現規律:每次將第一個數放到最後,其該序列的逆序數=上一個序列的逆序數+n-2*a[i]-1;由於以前有a[i]個逆序數,當放到後面後原來的逆序數變成了順序
而原來順序的變成了逆序,所以須要在原來序列逆序數的基礎上+(n-a[i]-1)-a[i],所以就是上面的式子。
以後咱們只需求出第一個序列的逆序數便可,而後經過一個循環就能夠找出最小的逆序數
代碼:
#include<iostream> #include<cstdio> #include<cmath> #include<algorithm> using namespace std; const int N=5010; struct node { int l,r,sum; }tr[4*N]; void push_up(int m) { tr[m].sum=tr[m<<1].sum+tr[m<<1|1].sum; } void buildtree(int t,int l,int r) { tr[t].l=l; tr[t].r=r; tr[t].sum=0; if(l==r) return; int mid=(l+r)>>1; buildtree(t<<1,l,mid); buildtree(t<<1|1,mid+1,r); } int query(int t,int l,int r) { if(tr[t].l>=l&&tr[t].r<=r) return tr[t].sum; int mid=(tr[t].l+tr[t].r)>>1; int ans=0; if(l<=mid) ans+=query(t<<1,l,r); if(r>mid) ans+=query(t<<1|1,l,r); return ans; } void update(int t,int id) { if(tr[t].l==id&&tr[t].r==id) { tr[t].sum=1; return; } int mid=(tr[t].l+tr[t].r)>>1; if(id<=mid) update(t<<1,id); else update(t<<1|1,id); push_up(t); } int main() { int n,i,j; int a[N]; while(~scanf("%d",&n)&&n!=0) { buildtree(1,0,n-1); int sum=0; for(i=0;i<n;i++) { scanf("%d",&a[i]); sum+=query(1,a[i]+1,n-1);//找比a[i]大的數的個數的和 update(1,a[i]);//將該值加入到線段樹中 } int ans=sum; for(i=0;i<n;i++) { sum+=n-2*a[i]-1; ans=min(ans,sum); } printf("%d\n",ans); } return 0; }
InputThe input consists of several test cases. The first line of the input is the number of the cases. There are no more than 10 cases.
For each case, the first line contains an integer N, 1<=N<=100,000, which is the number of the sticks of Pudge’s meat hook and the second line contains an integer Q, 0<=Q<=100,000, which is the number of the operations.
Next Q lines, each line contains three integers X, Y, 1<=X<=Y<=N, Z, 1<=Z<=3, which defines an operation: change the sticks numbered from X to Y into the metal kind Z, where Z=1 represents the cupreous kind, Z=2 represents the silver kind and Z=3 represents the golden kind.
OutputFor each case, print a number in a line representing the total value of the hook after the operations. Use the format in the example.
Sample Input
1 10 2 1 5 2 5 9 3
Sample Output
Case 1: The total value of the hook is 24.
解題思路:區間修改與區間求和(lazy標記)
#include<iostream> #include<cstdio> #include<cmath> #include<algorithm> using namespace std; const int N=100010; struct node { int sum,lazy; }tr[4*N]; void push_up(int m) { tr[m].sum=tr[m<<1].sum+tr[m<<1|1].sum; } void push_down(int t,int l,int r) { int mid=(l+r)>>1; tr[t<<1].lazy=tr[t<<1|1].lazy=tr[t].lazy; tr[t<<1].sum=tr[t].lazy*(mid-l+1); tr[t<<1|1].sum=tr[t].lazy*(r-mid); tr[t].lazy=0; } void buildtree(int t,int l,int r) { tr[t].lazy=0; if(l==r) { tr[t].sum=1; return; } int mid=(l+r)>>1; buildtree(t<<1,l,mid); buildtree(t<<1|1,mid+1,r); push_up(t); } void update(int t,int L,int R,int l,int r,int flag) { if(L<=l&&r<=R) { tr[t].lazy=flag; tr[t].sum=(r-l+1)*flag; return; } if(tr[t].lazy) push_down(t,l,r); int mid=(l+r)>>1; if(L<=mid) update(t<<1,L,R,l,mid,flag); if(mid<R) update(t<<1|1,L,R,mid+1,r,flag); push_up(t); } long long query(int a,int b,int l,int r,int t) { if(a<=l&&r<=b) return tr[t].sum; if(tr[t].lazy) push_down(t,l,r); int mid=(l+r)>>1; long long sum=0; if(a<=mid) sum+=query(a,b,l,mid,t<<1); if(b>mid) sum+=query(a,b,mid+1,r,t<<1|1); return sum; } int main() { int t,i,j; scanf("%d",&t); for(i=1;i<=t;i++) { int n,q; scanf("%d%d",&n,&q); buildtree(1,1,n); while(q--) { int a,b,flag; scanf("%d%d%d",&a,&b,&flag); update(1,a,b,1,n,flag); } printf("Case %d: The total value of the hook is %d.\n",i,tr[1].sum); } return 0; }
6.A - I Hate It
Input本題目包含多組測試,請處理到文件結束。
在每一個測試的第一行,有兩個正整數 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<iostream> #include<cstdio> #include<cmath> #include<algorithm> using namespace std; const int N=200010; struct node { int l,r,ans; }tr[N*4]; int pre[N]; void buildtree(int t,int l,int r) { tr[t].l=l; tr[t].r=r; if(l==r) { tr[t].ans=pre[l];//葉子節點賦值 return; } int mid=(l+r)>>1; buildtree(t<<1,l,mid); buildtree(t<<1|1,mid+1,r); tr[t].ans=max(tr[t<<1].ans,tr[t<<1|1].ans);//求出區間最大 } void update(int t,int id,int key)//單點修改 { if(tr[t].l==id&&tr[t].r==id) { tr[t].ans=key; return; } int mid=(tr[t].l+tr[t].r)>>1; if(id<=mid) update(t<<1,id,key); else update(t<<1|1,id,key); tr[t].ans=max(tr[t<<1].ans,tr[t<<1|1].ans);//更新區間的最大值 } int query(int t,int l,int r)//區間求最大值 { if(tr[t].l>=l&&tr[t].r<=r) return tr[t].ans; int mid=(tr[t].l+tr[t].r)>>1; int ans=-100; if(l<=mid)//找出左半邊的最大值 ans=query(t<<1,l,r); if(r>mid)//將做半邊的最大值與右半邊的最大值比較 ans=max(ans,query(t<<1|1,l,r)); return ans; } int main() { int n,m,i; while(~scanf("%d%d",&n,&m)) { for(i=1;i<=n;i++) { scanf("%d",&pre[i]); } buildtree(1,1,n); while(m--) { char s[2]; int a,b; scanf("%s",&s); scanf("%d%d",&a,&b); if(s[0]=='Q') { printf("%d\n",query(1,a,b)); } if(s[0]=='U') { update(1,a,b); } } } return 0; }
7.B - Billboard
InputThere are multiple cases (no more than 40 cases).
The first line of the input file contains three integer numbers, h, w, and n (1 <= h,w <= 10^9; 1 <= n <= 200,000) - the dimensions of the billboard and the number of announcements.
Each of the next n lines contains an integer number wi (1 <= wi <= 10^9) - the width of i-th announcement.OutputFor each announcement (in the order they are given in the input file) output one number - the number of the row in which this announcement is placed. Rows are numbered from 1 to h, starting with the top row. If an announcement can't be put on the billboard, output "-1" for this announcement.Sample Input
3 5 5 2 4 3 3 3
Sample Output
1 2 1 3 -1
代碼:
#include<iostream> #include<cstdio> #include<cmath> #include<algorithm> using namespace std; const int N=200010; int maxx[N*4]; int h,w,n; void push_up(int m) { maxx[m]=max(maxx[m<<1],maxx[m<<1|1]); } void build(int t,int l,int r) { if(l==r) { maxx[t]=w; return; } int mid=(l+r)>>1; build(t<<1,l,mid); build(t<<1|1,mid+1,r); push_up(t); } int update(int t,int val,int l,int r) { if(val>maxx[t]) return -1; if(l==r) { if(val<=maxx[t]) { maxx[t]-=val; return l; } else return -1; } int mid=(l+r)>>1; int flag=-1; flag=update(t<<1,val,l,mid); if(flag<0) flag=update(t<<1|1,val,mid+1,r); push_up(t); return flag; } int main() { int i,j; while(scanf("%d%d%d",&h,&w,&n)!=EOF) { if(h>n) h=n; build(1,1,h); int val; int k; for(i=1;i<=n;i++) { scanf("%d",&val); printf("%d\n",update(1,val,1,h)); } } return 0; }
總結:lazy標記線段樹須要增強,區間修改操做的不熟練須要增強。