4 1 1 1 1 14 2 1 2 2 2 3 2 4 1 2 3 1 2 2 1 2 2 2 3 2 4 1 1 4 2 1 2 1 2 2 2 3 2 4
1 1 1 1 1 3 3 1 2 3 4 1
题目大意:经典线段树模型。2种操作,单点查询,更新的时候是更新一群离散的点。
题目分析:好像就剩这种线段树没写过了,比赛的时候还真遇上了。。。于是开始苦逼的yy,好歹还是被yy出来了。30分钟搞定。只可惜一开始没信心,没敢写。。。好像有更高级的做法,不过作为菜鸟,还是苦逼的写了55棵线段树。。。
这题给了5w个点,但是更新的时候是更新一段区间内等距离的一些离散的点,单点更新肯定要TLE,所以必须另辟蹊径。更新的点是这样的,对于每个更新操作,给一个更新区间,[a,b],每次只更新[a,b]中(i - a)%k == 0的点,k给定的。k<=10。
整理一下发现,所要更新的点i = a + n*k,即i%k = a%k,而k<=10,于是对于每个k,每次更新的点就有k种情况,没错,就是k种情况,对于每个k,因为k有0~k-1,k个余数,k<=10的话,一共就有55种情况。所以维护55棵线段树。所以每次按余数来,对于每个更新操作,只要一次成段更新就ok了,查询的时候找到每个k对应的树,统计求和即可。
具体线段树的分布:
下标从0-54
当k= 1的时候,只有模k=0,所以第0棵线段树存模1的操作,其实就是起始的线段树;
当k=2的时候,有i%k=0和i%k = 1两种情况,所以对于所有%2=0的点,更新到第1棵线段树上,对所有%2=1的点,更新到第2棵线段树上;
当。。。。
以此类推,一共55棵线段树。
可能有点抽象,举个稍微具体的例子:
例如给一个更新操作a=5,b=13,k = 3,那么实际上我们需要更新的点只有5,8,11,这3个数都有一个共同的特点就是模3为2,那么模3为2对应第5棵线段树,那么我们只需要在第5棵线段树上成段更新区间5~13即可,只要更新5,8,11三个点,为什么我们要更新一个区间呢,因为这根本不影响我们查询结果,比如我们要统计第5个点的值,那么我们只要依次统计5%1,5%2,5%3....5%10这10棵线段树上的第5个点上值的和就可以了,其中第5棵线段树上的第5个点的值就是5%3的时候更新的结果,所以虽然我们在第5棵线段树上更新了一个区间,但实际上查询的时候只有模3余2的点才能查询到这棵线段树,所以刚才的成段更新是没问题的。比如要查询第9个点的值,虽然我们在第5棵线段树上更新了第9个点,但实际查询的时候,9%3==0,只能查询到第3棵线段树,第5棵线段树对第9个点的值是没有影响的。
详情请见代码:
#include <iostream> #include<cstdio> #include<cstring> #include<cstdlib> #include<cmath> #include<algorithm> #include<map> #include<set> #include<vector> #include<queue> using namespace std; const int N = 50001; const double eps = 1e-6; const double PI = acos(-1.0); int n,m; int kid[] = {0,0,1,3,6,10,15,21,28,36,45}; struct node { int lcm[N + N + N]; void build(int num,int s,int e) { lcm[num] = 0; if(s == e) return; int mid = (s + e)>>1; build(num<<1,s,mid); build(num<<1|1,mid + 1,e); } void insert(int num,int s,int e,int l,int r,int val) { if(s == l && r == e)//成段更新,省去了lazy标记 { lcm[num] += val; return; } if(lcm[num])//下传 { lcm[num<<1] += lcm[num]; lcm[num<<1|1] += lcm[num]; lcm[num] = 0; } int mid = (s + e)>>1; if(r <= mid) insert(num<<1,s,mid,l,r,val); else { if(l > mid) insert(num<<1|1,mid + 1,e,l,r,val); else { insert(num<<1,s,mid,l,mid,val); insert(num<<1|1,mid + 1,e,mid + 1,r,val); } } } int query(int num,int s,int e,int pos) { if(s == e) return lcm[num]; if(lcm[num])//下传 { lcm[num<<1] += lcm[num]; lcm[num<<1|1] += lcm[num]; lcm[num] = 0; } int mid = (s + e)>>1; if(pos <= mid) return query(num<<1,s,mid,pos); else return query(num<<1|1,mid + 1,e,pos); } }tree[55]; int main() { int i; int t; int a,b,k,op,c; while(scanf("%d",&n) != EOF) { for(i = 0;i < 55;i ++) tree[i].build(1,1,n); for(i = 1;i <= n;i ++) { scanf("%d",&t); tree[0].insert(1,1,n,i,i,t); } scanf("%d",&m); while(m --) { scanf("%d",&op); if(op == 2) { int ans = 0; scanf("%d",&a); for(i = 1;i <= 10;i ++) { int id = kid[i] + a % i; ans += tree[id].query(1,1,n,a); } printf("%d\n",ans); } else { scanf("%d%d%d%d",&a,&b,&k,&c); int id = kid[k] + a % k; tree[id].insert(1,1,n,a,b,c); } } } return 0; } //312MS 28728K
没想到这题代码这么段,写的也比较顺利,可是效率好像不高。。。
不过这题最好还是用树状数组写,速度快,写起来比较方便,更重要的是省空间。这题开55棵线段树很容易爆内存的。因为操作不是很复杂,只要维护一个前缀和就行了,所以树状数组解此题更适合。