hdu 4893 2014多校联合第三场 1007 线段树

本文介绍了一种处理特定序列操作的高效算法,包括添加值、查询区间和将区间内元素转换为最接近的Fibonacci数。通过在线段树上维护两个值并使用'延时标记'技术,实现在不同操作下的快速响应,确保每个请求在小于10ms的时间内完成。
摘要由CSDN通过智能技术生成

http://acm.hdu.edu.cn/showproblem.php?pid=4893

Problem Description
Recently, Doge got a funny birthday present from his new friend, Protein Tiger from St. Beeze College. No, not cactuses. It's a mysterious blackbox.

After some research, Doge found that the box is maintaining a sequence an of n numbers internally, initially all numbers are zero, and there are THREE "operations":

1.Add d to the k-th number of the sequence.
2.Query the sum of ai where l ≤ i ≤ r.
3.Change ai to the nearest Fibonacci number, where l ≤ i ≤ r.
4.Play sound "Chee-rio!", a bit useless.

Let F 0 = 1,F 1 = 1,Fibonacci number Fn is defined as F n = F n - 1 + F n - 2 for n ≥ 2.

Nearest Fibonacci number of number x means the smallest Fn where |F n - x| is also smallest.

Doge doesn't believe the machine could respond each request in less than 10ms. Help Doge figure out the reason.
 

Input
Input contains several test cases, please process till EOF.
For each test case, there will be one line containing two integers n, m.
Next m lines, each line indicates a query:

1 k d - "add"
2 l r - "query sum"
3 l r - "change to nearest Fibonacci"

1 ≤ n ≤ 100000, 1 ≤ m ≤ 100000, |d| < 2 31, all queries will be valid.
 

Output
For each Type 2 ("query sum") operation, output one line containing an integer represent the answer of this query.
 

Sample Input
  
  
1 1 2 1 1 5 4 1 1 7 1 3 17 3 2 4 2 1 5
 

Sample Output
  
  
0 22
题目大意:给定一个区间(1,n)初始时刻区间内所有元素全为零,现在我们对于“1“命令下的操作,将第k个数的值增加d,对于“2”命令下的操作将(x,y)区间内的和输出,对于”3 “命令下的操作,要求将区间(x,y)内的每一个数变为与其最接近的一个Fibonacci数。

解题思路:关键是命令3的处理,我们可以把每段序列最接近的Fibonacci数求出来,在线段树里存放两个值,一个是真实值,一个是最接近的Fibonacci值,在执行3操作时采用“延时标记”的方法为需要把真值变成Fibonacci值的区间标记下来,在查询的时候,加以判断。

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int MAXN=999999;
LL ab(LL x)
{
    if(x<0)
        return -x;
    return x;
}
LL fib[100];
void get_fib()
{
    fib[1]=fib[0]=1;
    for(int i=2;i<=90;i++)
        fib[i]=fib[i-1]+fib[i-2];
}
LL near_fib(LL x)
{
    int l=0,r=90,mid;
    int ans=0;
    while(l<=r)
    {
        mid=(l+r)/2;
        if(fib[mid]>=x)
        {
            ans=mid;
            r=mid-1;
        }
        else
            l=mid+1;
    }
    if(ans==0)
        return 1;
    if(ab(fib[ans-1]-x)<=ab(fib[ans]-x))
        return fib[ans-1];
    return fib[ans];
}
struct SegmentTree
{
    struct Tree
    {
        int l,r;
        LL sum;
        LL f;
        bool fg;
    };
    Tree tree[MAXN*4];
    void push_down(int root)//向下维护至叶子节点,如果被标记了的话,我们就把其子节点全部标记,并且更新其sum值为f值,并同时解除本身的标记。
    {                       //如果没被标记,那么此函数不进行任何操作。
        if(tree[root].fg)
        {
            if(tree[root].l!=tree[root].r)
                tree[root<<1].fg=tree[root<<1|1].fg=true;
            else
                tree[root].sum=tree[root].f;
            tree[root].fg=false;
        }
    }
    void push_up(int root)//向上维护至根节点,如果该节点被标记返回f值,没被标记返回sum值
    {
        LL sum=0;
        if(tree[root<<1].fg)
            sum+=tree[root<<1].f;
        else
            sum+=tree[root<<1].sum;
        if(tree[root<<1|1].fg)
            sum+=tree[root<<1|1].f;
        else
            sum+=tree[root<<1|1].sum;
        tree[root].sum=sum;
        tree[root].f=tree[root<<1].f+tree[root<<1|1].f;
    }
    void build(int root,int l,int r)//构造线段树
    {
        tree[root].l=l;
        tree[root].r=r;
        tree[root].fg=false;
        if(tree[root].l==tree[root].r)
        {
            tree[root].sum=0;
            tree[root].f=1;
            return;
        }
        int mid=(l+r)/2;
        build(root<<1,l,mid);
        build(root<<1|1,mid+1,r);
        push_up(root);
    }
    void update(int root,int pos,int val)
    {
        push_down(root);
        if(tree[root].l==tree[root].r)
        {
            tree[root].sum+=val;
            tree[root].f=near_fib(tree[root].sum);
            //这里需要特别注意,在更新其真值的时候就把其对应的斐波那契数列数求出来放在f里面
            return;
        }
        int mid=(tree[root].l+tree[root].r)/2;
        if(pos<=mid)
            update(root<<1,pos,val);
        else
            update(root<<1|1,pos,val);
        push_up(root);//向上维护,把其祖先节点全部变为相应的更新后的值
    }
    void dfs(int root,int L,int R)//将要查询区间上的值改为标记过的,否则的话查询时是错误的。比如标记的(1,10)而查询的时候是(4,7)这一步dfs就凸显作用了
    {
        push_down(root);
        if(L<=tree[root].l&&R>=tree[root].r)
        {
            if(tree[root].l!=tree[root].r)
               push_up(root);
            return;
        }
        int mid=(tree[root].l+tree[root].r)/2;
        if(L<=mid)
            dfs(root<<1,L,R);
        if(R>mid)
            dfs(root<<1|1,L,R);
        push_up(root);
    }
    LL query(int root,int L,int R)
    {
        if(L<=tree[root].l&&R>=tree[root].r)
        {
            if(tree[root].fg)
                return tree[root].f;
            return tree[root].sum;
        }
        int mid=(tree[root].l+tree[root].r)/2;
        LL ret=0;
        if(L<=mid)
            ret+=query(root<<1,L,R);
        if(R>mid)
            ret+=query(root<<1|1,L,R);
        return ret;
    }
    void change(int root,int L,int R)
    {
        push_down(root);
        if(L<=tree[root].l&&R>=tree[root].r)
        {
            tree[root].fg=true;
            return;
        }
        int mid=(tree[root].l+tree[root].r)/2;
        if(L<=mid)
            change(root<<1,L,R);
        if(R>mid)
            change(root<<1|1,L,R);
        push_up(root);
    }
}tr;
int n,m;
int main()
{
    get_fib();
    while(~scanf("%d%d",&n,&m))
    {
        tr.build(1,1,n);
        for(int i=0;i<m;i++)
        {
            int p,x,y;
            scanf("%d%d%d",&p,&x,&y);
            if(p==1)
            {
                tr.update(1,x,y);
            }
            else if(p==3)
            {
                tr.change(1,x,y);
            }
            else
            {
                tr.dfs(1,x,y);
                LL ans=tr.query(1,x,y);
                printf("%I64d\n",ans);
            }
        }
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值