zoukankan      html  css  js  c++  java
  • P3203 [HNOI2010]弹飞绵羊 —— 懒标记?分块?

    好久没写博客了哈,今天来水一篇。_(:з」∠)_

    题目 :弹飞绵羊(一道省选题)

    题目描述

    某天,Lostmonkey发明了一种超级弹力装置,为了在他的绵羊朋友面前显摆,他邀请小绵羊一起玩个游戏。游戏一开始,Lostmonkey在地上沿着一条直线摆上n个装置,每个装置设定初始弹力系数ki,当绵羊达到第i个装置时,它会往后弹ki步,达到第i+ki个装置,若不存在第i+ki个装置,则绵羊被弹飞。绵羊想知道当它从第i个装置起步时,被弹几次后会被弹飞。为了使得游戏更有趣,Lostmonkey可以修改某个弹力装置的弹力系数,任何时候弹力系数均为正整数。

    输入输出格式

    输入格式:
    第一行包含一个整数n,表示地上有n个装置,装置的编号从0到n-1。

    接下来一行有n个正整数,依次为那n个装置的初始弹力系数。

    第三行有一个正整数m,

    接下来m行每行至少有两个数i、j,若i=1,你要输出从j出发被弹几次后被弹飞,若i=2则还会再输入一个正整数k,表示第j个弹力装置的系数被修改成k。

    输出格式:
    对于每个i=1的情况,你都要输出一个需要的步数,占一行。

    输入输出样例

    输入样例#1:
    4
    1 2 1 1
    3
    1 1
    2 1 1
    1 1

    输出样例#1:
    2
    3

    说明

    对于20%的数据n,m<=10000,对于100%的数据n<=200000,m<=100000

    分析(1)

    首先,本人拿到这篇题目的时候脑子是没有转过来的。那时候我在想什么呢?。。。对,当我们修改了某个点的k值之后,那么这个操作对于后面的点来说是没有丝毫的影响的,但却会使其前面的指向它的节点造成影响(因为一开始我没用分块嘛,直接用了一个比较暴力的思想:ans存答案,来做这道题的),于是乎觉得这样做太暴力,然后就弄了个懒标记和染色(但是有点复杂的样子于是乎挂了)。然后就直接一个朴素的懒标记骗了个50,TLE 五个点。

    代码如下。

    #include<bits/stdc++.h>
    using namespace std;
    const int M=2e5+100;
    inline int read(){
        int x=0; char c=getchar();
        while(!isdigit(c)) c=getchar();
        for(;isdigit(c);c=getchar()) x=x*10+c-'0';
        return x;
    }
    int n,m,tag;
    int k[M],ans[M];
    inline void dfs(int to){  //更新区间内节点的ans值
        for(int i=tag-1;i>=to;--i)
            ans[i]=ans[i+k[i]]+1;
    }
    int main(){
        n=read();
        for(int i=0;i<n;++i)
            k[i]=read();
        for(int i=n-1;i>=0;--i){
            if(i+k[i]>=n) ans[i]=1;
            else ans[i]=ans[i+k[i]]+1;
        }
        m=read();
        while(m--){
            int op=read();
            if(op==1){
                int now=read();
                if(tag>now) dfs(now); //向前更新节点的ans值,直到当前的节点
                printf("%d
    ",ans[now]);
            }
            else if(op==2){
                int now=read(),nwk=read();
                if(nwk==k[now]) continue;
                if(tag>now) dfs(now);  //原本的懒标记在后面那么先将now~tag的节点的ans值更新
                tag=now;    //懒标记记录下当前修改的位置
                k[now]=nwk; int to=now+k[now];
                if(to>=n) ans[now]=1;
                else ans[now]=ans[to]+1;
            }
        }
        return 0;
    } 

    那么我们先不进行分块解法的讨论,首先看看一道简单的分块题来熟(复)悉(习)一下分块这个算法吧。(如果你是初学,请点这里

    Title :A Simple Problem with Integers

    Description

    You have N integers, A1, A2, … , AN. You need to deal with two kinds of operations. One type of operation is to add some given number to each number in a given interval. The other is to ask for the sum of numbers in a given interval.
    //概述一下,就是区间加以及区间求和(简直就是模板题),另外提一下这个东西也可以用线段树做

    Input

    The first line contains two numbers N and Q. 1 ≤ N,Q ≤ 1e5.
    //表示有n(不超过1e5)个数字,Q(不超过1e5)个操作
    The second line contains N numbers, the initial values of A1, A2, … , AN. -1e9 ≤ Ai ≤ 1e9.
    //第二行有n个数字,都是int/2的范围内的(但是加起来是会爆int的)
    Each of the next Q lines represents an operation.
    //表示接下来Q行是Q个操作
    “C a b c” means adding c to each of Aa, Aa+1, … , Ab. -1e4 ≤ c ≤ 1e4.
    //C: 表示对a~b进行区间加操作
    “Q a b” means querying the sum of Aa, Aa+1, … , Ab.
    //Q: 表示询问a~b的区间和

    Output

    You need to answer all Q commands in order. One answer in a line. //回答询问,每行一个答案

    Sample Input

    10 5
    1 2 3 4 5 6 7 8 9 10
    Q 4 4
    Q 1 10
    Q 2 4
    C 3 6 3
    Q 2 4

    Sample Output

    4
    55
    9
    15

    Hint

    The sums may exceed the range of 32-bit integers.
    //可能会爆int(就是要你开long long)

    代码如下:

    #include<iostream>
    #include<cmath>
    #include<cstdio>
    #include<math.h>
    typedef long long ll;
    using namespace std;
    const int M=1e5+100;
    inline ll read(){
        ll x=0,f=1; char c=getchar();
        for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
        for(;isdigit(c);c=getchar()) x=x*10+c-'0';
        return x*f;
    }
    int n,q,block,num,l[M],r[M];
    ll blg[M],a[M],d[M],sum[M];
    inline void build(){ //建立分块
        block=sqrt((double)n);
        num=n/block; if(n%block) ++num; 
        for(int i=1;i<=num;++i)
            l[i]=(i-1)*block+1,r[i]=i*block;
        r[num]=n;
        for(int i=1;i<=n;++i)
            blg[i]=(i-1)/block+1,sum[blg[i]]+=a[i];
    }
    inline void update(int x,int y,int k){ //一个更新区间的操作
        if(blg[x]==blg[y]){
            sum[blg[x]]+=(y-x+1)*k;
            for(int i=x;i<=y;++i)
                a[i]+=k;
            return ;
        }
        sum[blg[x]]+=(r[blg[x]]-x+1)*k;
        sum[blg[y]]+=(y-l[blg[y]]+1)*k;
        for(int i=x;i<=r[blg[x]];++i) a[i]+=k;
        for(int i=l[blg[y]];i<=y;++i) a[i]+=k;
        for(int i=blg[x]+1;i<blg[y];++i) d[i]+=k;
    }
    ll query(int x,int y){ //询问区间加的操作
        ll ans=0;
        if(blg[x]==blg[y]){
            for(int i=x;i<=y;++i)
                ans+=a[i]+d[blg[i]];
            return ans;
        }
        for(int i=x;i<=r[blg[x]];++i) ans+=a[i]+d[blg[i]];
        for(int i=l[blg[y]];i<=y;++i) ans+=a[i]+d[blg[i]];
        for(int i=blg[x]+1;i<blg[y];++i) ans+=sum[i]+block*d[i];
        return ans;
    }
    
    int main(){
        n=read();q=read();
        for(int i=1;i<=n;++i)
            a[i]=read();
        build();
        while(q--){
            char op=getchar();
            while(!isupper(op)) op=getchar();
            int L=read(),R=read();
            if(op=='Q') printf("%lld
    ",query(L,R));
            else update(L,R,read());
        }
        return 0;
    }

    于是一道模板题热热身之后,大家应该有些分块思路了吧、?


    分析(2)

    于是乎该怎么办呢?(这个懒标记骗的分不满意啊)那么经过深思熟虑之后,我终于发现了这道题原来是可以用分块暴力来做的。具体怎么实现呢?其实就是说我们要维护某个点的话,就是维护他所在的那块区间里的值。什么值呢? 第一个值是该节点跳出该区间所需的步数,第二个值是该节点跳出该区间后到达的下一个节点的位置(注意下一个节点不一定在该区间相邻的区间内)。于是乎这道题我就用个分块维护区间信息的方法A了此题。那么为什么用分块做效率较高呢?因为分块时我们对于区间的操作只有一个预处理(n)+分块询问、维护(m*sqrt(n))的时间复杂度,即:O(n+m*sqrt(n)),这已经算是对于此问题一个较优的解法了(当然更优的还有动态树lct)。

    代码如下:

    #include<bits/stdc++.h>
    using namespace std;
    const int M=2e5+100;
    inline int read(){ //快读 
        int x=0; char c=getchar();
        while(!isdigit(c)) c=getchar();
        for(;isdigit(c);c=getchar()) x=x*10+c-'0';
        return x;
    }
    int n,m,block,num;
    int k[M],l[M],r[M],blg[M],ans[M],to[M];
    
    inline void build(){  //建立分块区间 
        block=sqrt(n);
        num=n/block; if(n%block) ++num;
        for(int i=1;i<=num;++i)
            l[i]=(i-1)*block+1,r[i]=i*block;
        r[num]=n;
        for(int i=1;i<=n;++i)
            blg[i]=(i-1)/block+1;
    }
    
    inline void work(int x,int y){ //分块区间维护 
        for(int i=y;i>=x;--i){
            int nxt=i+k[i];
            (nxt>r[blg[i]])?
            (ans[i]=1,to[i]=nxt):
            (ans[i]=ans[nxt]+1,to[i]=to[nxt]);
        }
    }
    
    inline int query(int now){ //单点询问
        int res=ans[now],nxt=to[now];
        for(int i=blg[now]+1;nxt<=n;++i)
            res+=ans[nxt],nxt=to[nxt];
        return res;
    } 
    
    int main(){
        n=read(); build();
        for(int i=1;i<=n;++i)
            k[i]=read();
        work(1,n);  //先维护一下整个区间 
        m=read();
        while(m--){
            int op=read();
            if(op==1){
                int now=read()+1;
                printf("%d
    ",query(now));
            }
            else{
                int now=read()+1,kk=read();
                k[now]=kk;
                work(l[blg[now]],r[blg[now]]); //这里只需维护单个分块区间 
            }
        }
        return 0;
    }

    于是乎,这道题我们就可以愉快的用分块A了。

    然后这道题貌似也没什么好说的了。。。那么,拜拜! _(:з」∠)_

    ヾ( ̄▽ ̄)Bye~Bye~

  • 相关阅读:
    AODV点点滴滴
    让控件的DropdownMenu或者PopupMenu弹出来
    如何让CoolBar控件的Bands在同一行上
    怎样在InstallShield的Basic MSI Project中用InstallScript添加路径
    VC调用Delphi制作的动态链接库如何互相传递字符串
    Delphi 中用 GetEnvironmentVariable 获取常用系统变量
    如何在工具栏或者其他的控件上显示其他控件的Hint
    企业信息开发平台(1)序
    对.Net 垃圾回收的C#编程相关方面(Finalize 和Dispose(bool disposing)和 Dispose())的一些理解体会(转)
    android上怎样让一个Service开机自动启动
  • 原文地址:https://www.cnblogs.com/Judge/p/9383037.html
Copyright © 2011-2022 走看看