士兵杀敌(四)
-
描述
-
南将军麾下有百万精兵,现已知共有M个士兵,编号为1~M,每次有任务的时候,总会有一批编号连在一起人请战(编号相近的人经常在一块,相互之间比较熟悉),最终他们获得的军功,也将会平分到每个人身上,这样,有时候,计算他们中的哪一个人到底有多少军功就是一个比较困难的事情,军师小工的任务就是在南将军询问他某个人的军功的时候,快速的报出此人的军功,请你编写一个程序来帮助小工吧。
假设起始时所有人的军功都是0.
-
输入
-
只有一组测试数据。
每一行是两个整数T和M表示共有T条指令,M个士兵。(1<=T,M<=1000000)
随后的T行,每行是一个指令。
指令分为两种:
一种形如
ADD 100 500 55 表示,第100个人到第500个人请战,最终每人平均获得了55军功,每次每人获得的军功数不会超过100,不会低于-100。
第二种形如:
QUERY 300 表示南将军在询问第300个人的军功是多少。
输出
- 对于每次查询输出此人的军功,每个查询的输出占一行。 样例输入
-
4 10 ADD 1 3 10 QUERY 3 ADD 2 6 50 QUERY 3
样例输出
-
10 60
做法一:树状数组:主要用于查询任意两位之间的所有元素之和,但是每次只能修改一个元素的值;经过简单修改可以在log(n)的复杂度下进行范围修改,但是这时只能查询其中一个元素的值(如果加入多个辅助数组则可以实现区间修改与区间查询)。
void Update(int i,int value,int n) //从前往后更新 { while(i<=n) { C[i]+=value; i+=i&(-i); } } int sum(int x) //求和 0到x { int ans=0; while(x>0) { ans+=a[x]; x-=x&(-x); } return ans; }
void update(int star,int value) //从后往前更新 { while(star>0) { add[star]+=value; star-=star&(-star); } } int sum(int end) { int ans=0; while(end<=m) { ans+=add[end]; end+=end&(-end); } return ans; }
插线问点:向[star,end]这个区间加value,就可以看做是向[0,end]加value,然后向[0,star-1]加-value。利用update实现,然后就可以问点了,用sum函数问
插点问线:直接update
插线问线:首先是引入delta数组 delta[i]表示区间 [i, n] 的共同增量 于是修改区间 [l, r] 时修改 delta[l] 和 delta[r + 1] 即可(就是差分的思路)
查询的时候是查询区间 [l, r] 的和 即sum[r] - sum[l - 1] 所以现在的问题是求sum[i]
#include<stdio.h> #include<string.h> int C[1000005],t,m; void Update(int star,int value) //A[i]的改变值为value { while(star<=m) { C[star]+=value; star+=star&(-star); } } int Sum(int x) //求和 0到x { int ans=0; while(x>0) { ans+=C[x]; x-=x&(-x); } return ans; } int main() { int i,j; char str[15]; scanf("%d %d",&t,&m); memset(C,0,sizeof(C)); for(i=1;i<=t;i++) { scanf("%s",str); if(strcmp(str,"ADD")==0) { int l,r,c; scanf("%d %d %d",&l,&r,&c); Update(l,c); Update(r+1,-c); } else if(strcmp(str,"QUERY")==0) { int x; scanf("%d",&x); printf("%d\n",Sum(x)); } } return 0; }
或者
#include<stdio.h> #include<string.h> int C[1000005],t,m; void update(int star,int value) { while(star>0) { C[star]+=value; star-=star&(-star); } } int sum(int end) { int ans=0; while(end<=m) { ans+=C[end]; end+=end&(-end); } return ans; } int main() { int i,j; char str[15]; scanf("%d %d",&t,&m); memset(C,0,sizeof(C)); for(i=1;i<=t;i++) { scanf("%s",str); if(strcmp(str,"ADD")==0) { int l,r,c; scanf("%d %d %d",&l,&r,&c); update(r,c); update(l-1,-c); } else if(strcmp(str,"QUERY")==0) { int x; scanf("%d",&x); printf("%d\n",sum(x)); } } return 0; }
做法二:线段树
做法三:差分数组(本题不推荐)
差分数组修改一次区间数据,就要维护一次数组 ,所以适用于多次修改后查询
#include<stdio.h> #include<string.h> #include<iostream> using namespace std; int d[1000001]; int a[1000001]; int sum[1000001]; int main() { int t,m,i,j,x; char str[10]; scanf("%d %d",&t,&m); for(i=0;i<=m;i++) a[i] = 0; for(i=1;i<=m;i++) d[i]=a[i]-a[i-1]; d[0] = a[0]; for(j=1;j<=t;j++) { scanf("%s",str); if(strcmp(str,"ADD")==0) { int l,r,c; scanf("%d %d %d",&l,&r,&c); d[l] += c; d[r+1] -=c; } else if(strcmp(str,"QUERY")==0) { scanf("%d",&x); for(i=0;i<=x;i++) sum[i] = 0; for (i = 1; i <= x; i++) { if(i==1) sum[i] = d[i]; else sum[i] = d[i] + sum[i-1]; } printf("%d\n",sum[x]); } } return 0; }