hdu4267 线段树

这题时间卡的真紧,内存卡的更紧。。。

做这题时写了三份代码,

第一份是最基础的线段树区间更新,记录更新条件,开一颗线段树记录值,一颗记录每段区间的参考区间,一颗记录增加量,结果果断超时,数据强的一逼。

然后苦逼的空间换时间,开十棵线段树,分别记录对几取余,然后每次pushdown时需要哪个值就更新那个值,query时再全部更新,本以为过题目妥妥的,结果尼玛又超时,题目数据强的不止一逼。

最后再开大招,空间继续换时间,开了55棵线段树,这样一次只需要维护一颗,每次更新时不需要将之前的pushdown可以直接加上,55个20W的数组,尼玛第一次开这么大,最后空间险过,时间400多ms。

解释一下这题的解题思路:

线段树记录每个值的变化情况,另开一个数组记录初始情况。

对于所有取余情况,总共有55种,对1取余1种,对2取余2种。。。。然后对(i-l)%k=0,可变成i%k==l%k,这样对于一个区间,便可以根据其取余情况记录该区间应该更新哪一棵线段树,然后对这颗线段树这一区间的所有值都进行更新(延迟更新,查询时再更新),这样可能有人会问,对所有值都更新岂不不满足题意了?关键是,取结果的时候,位置确定,对1,对2,对3....的取余结果都能计算出来,也就是说,取值的时候取的线段树是确定的,不是所有的树都取,有些值虽然进行了更新,但是它可能永远都取不到,所以不会对结果造成影响。

具体参见代码:

#include <iostream>
#include <cstring>
#include <cstdio>

using namespace std;

#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define delf int m=(l+r)>>1
const int MAX=50010;
int sum[55][MAX<<2];        //55棵记录取余情况
int col[MAX<<2];            //记录是否更新过
int re[MAX];                //记录每个位置初始值
int lo[11];                 //计算位置

void pushdown(int rt)
{
    if (col[rt]==0)
        return ;
    col[rt]=0;
    for (int i=0;i<55;i++)      //要更新就要全部更新,才能使col变成0表示该节点已更新过,更新过或无需更新的节点sum=0,更新无影响
    {
        sum[i][rt<<1]+=sum[i][rt];
        sum[i][rt<<1|1]+=sum[i][rt];
        sum[i][rt]=0;
    }
    col[rt<<1]=col[rt<<1|1]=1;
    return ;
}

void build(int l,int r,int rt)      //初始化
{
    col[rt]=0;
    for (int i=0;i<55;i++)
        sum[i][rt]=0;
    if (l==r)
        return ;
    delf;
    build(lson);
    build(rson);
    return ;
}

void update(int L,int R,int k,int c,int l,int r,int rt)
{
    if (L<=l&&r<=R)     //如果找到了满足题意的区间,便记录下来
    {
        int t=lo[k-1]+L%k;      //计算应该更新哪一棵线段树,依据l%k==i%k
        col[rt]=1;
        sum[t][rt]+=c;          //如果之前这个位置有值,直接加上即可,省去了一次pushdown
        return ;
    }
    delf;
    if (L<=m)
        update(L,R,k,c,lson);
    if (R>m)
        update(L,R,k,c,rson);
    return ;
}

int query(int k,int l,int r,int rt)
{
    if (l==r)           //找到查询位置
    {
        int s=0;
        for (int i=1;i<=10;i++)         //在55棵线段树中找有用的线段树
            s+=sum[k%i+lo[i-1]][rt];            //有用的线段树的编号可以计算出来,然后计算所有更新情况的和
        return s;
    }
    pushdown(rt);
    delf;
    if (m>=k)
        return query(k,lson);
    else
        return query(k,rson);
}

int main()
{
    int N;
    lo[0]=0;
    lo[1]=1;
    for (int i=2;i<=10;i++)         //计算线段树位置
        lo[i]=i+lo[i-1];
    while (~scanf("%d",&N))
    {
        build(1,N,1);
        for (int i=1;i<=N;i++)
            scanf("%d",&re[i]);
        int q;
        scanf("%d",&q);
        int p,a,b,k,c;
        for (int i=1;i<=q;i++)
        {
            scanf("%d",&p);
            if (p==1)
            {
                scanf("%d%d%d%d",&a,&b,&k,&c);
                update(a,b,k,c,1,N,1);
            }
            else
            {
                scanf("%d",&k);
                int s=re[k];
                s+=query(k,1,N,1);      //加上初始值便是结果
                printf("%d\n",s);
            }
        }
    }
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值