A Simple Problem with Integers
Time Limit: 5000/1500 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 2520 Accepted Submission(s): 814
The first line contains an integer N. (1 <= N <= 50000)
The second line contains N numbers which are the initial values of A1, A2, ... , AN. (-10,000,000 <= the initial value of Ai <= 10,000,000)
The third line contains an integer Q. (1 <= Q <= 50000)
Each of the following Q lines represents an operation.
"1 a b k c" means adding c to each of Ai which satisfies a <= i <= b and (i - a) % k == 0. (1 <= a <= b <= N, 1 <= k <= 10, -1,000 <= c <= 1,000)
"2 a" means querying the value of Aa. (1 <= a <= N)
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棵线段树很容易爆内存的。因为操作不是很复杂,只要维护一个前缀和就行了,所以树状数组解此题更适合。