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

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 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
 

Sample Output
   
   
   
   
1 1 1 1 1 3 3 1 2 3 4 1
 

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;
}
//312MS	28728K

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

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


你可能感兴趣的:(数据结构)