hdu4267 A Simple Problem with Integers(多棵线段树)

来源:互联网 发布:平面构成 知乎 编辑:程序博客网 时间:2024/06/05 05:52

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


Problem Description
Let A1, A2, ... , AN be N elements. You need to deal with two kinds of operations. One type of operation is to add a given number to a few numbers in a given interval. The other is to query the value of some element.
 

Input
There are a lot of test cases. 
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)
 

Output
For each test case, output several lines to answer all query operations.
 

Sample Input
4 1 1 1 1142 12 22 32 41 2 3 1 22 1 2 22 32 41 1 4 2 12 12 22 32 4
 

Sample Output
111113312341
 

Source
2012 ACM/ICPC Asia Regional Changchun Online

题目大意:经典线段树模型。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;}//312MS28728K

没想到这题代码这么段,写的也比较顺利,可是效率好像不高。。。

不过这题最好还是用树状数组写,速度快,写起来比较方便,更重要的是省空间。这题开55棵线段树很容易爆内存的。因为操作不是很复杂,只要维护一个前缀和就行了,所以树状数组解此题更适合。