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

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

题目描述

某天,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\n",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\n",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\n",query(now));
}
else{
int now=read()+1,kk=read();
k[now]=kk;
work(l[blg[now]],r[blg[now]]); //这里只需维护单个分块区间
}
}
return 0;
}

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

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

ヾ( ̄▽ ̄)Bye~Bye~

P3203 [HNOI2010]弹飞绵羊 —— 懒标记?分块?的更多相关文章

  1. P3203 [HNOI2010]弹飞绵羊 —— 懒标记?分块?LCT?...FAQ orz

    好久没写博客了哈,今天来水一篇._(:з」∠)_ 题目 :弹飞绵羊(一道省选题) 题目描述 某天,Lostmonkey发明了一种超级弹力装置,为了在他的绵羊朋友面前显摆,他邀请小绵羊一起玩个游戏.游戏 ...

  2. P3203 [HNOI2010]弹飞绵羊(LCT)

    P3203 [HNOI2010]弹飞绵羊 LCT板子 用一个$p[i]$数组维护每个点指向的下个点. 每次修改时cut*1+link*1就解决了 被弹出界时新设一个点,权为0,作为终点表示出界点.其他 ...

  3. 洛谷 P3203 [HNOI2010]弹飞绵羊 解题报告

    P3203 [HNOI2010]弹飞绵羊 题目描述 某天,Lostmonkey发明了一种超级弹力装置,为了在他的绵羊朋友面前显摆,他邀请小绵羊一起玩个游戏.游戏一开始,Lostmonkey在地上沿着一 ...

  4. [Luogu P3203] [HNOI2010]弹飞绵羊 (LCT维护链的长度)

    题面 传送门:洛谷 Solution 这题其实是有类似模型的. 我们先考虑不修改怎么写.考虑这样做:每个点向它跳到的点连一条边,最后肯定会连成一颗以n+1为根的树(我们拿n+1代表被弹出去了).题目所 ...

  5. 洛谷P3203 [HNOI2010] 弹飞绵羊 [LCT]

    题目传送门 弹飞绵羊 题目描述 某天,Lostmonkey发明了一种超级弹力装置,为了在他的绵羊朋友面前显摆,他邀请小绵羊一起玩个游戏.游戏一开始,Lostmonkey在地上沿着一条直线摆上n个装置, ...

  6. P3203 [HNOI2010]弹飞绵羊(LCT)

    弹飞绵羊 题目传送门 解题思路 LCT. 将每个节点的权值设为\(1\),连接\(i\)和\(i+ki\),被弹飞就连上\(n\),维护权值和\(sum[]\).从\(j\)弹飞需要的次数就是\(sp ...

  7. 【BZOJ2002】[HNOI2010] 弹飞绵羊(大力分块)

    点此看题面 大致题意: 有\(n\)个弹力装置,当到达第\(i\)个装置时,会被弹到第\(i+k_i\)个装置,若不存在第\(i+k_i\)个装置,就会被弹飞.有两种操作,一种操作是将\(k_x\)改 ...

  8. 洛谷P3203 [HNOI2010]弹飞绵羊(LCT,Splay)

    洛谷题目传送门 关于LCT的问题详见我的LCT总结 思路分析 首先分析一下题意.对于每个弹力装置,有且仅有一个位置可以弹到.把这样的一种关系可以视作边. 然后,每个装置一定会往后弹,这不就代表不存在环 ...

  9. [洛谷P3203][HNOI2010]弹飞绵羊

    题目大意:有$n$个节点,第$i$个节点有一个弹力系数$k_i$,当到达第$i$个点时,会弹到第$i+k_i$个节点,若没有这个节点($i+k_i>n$)就会被弹飞.有两个操作: $x:$询问从 ...

随机推荐

  1. python 生成器和各种推导式

    ##################################总结############################### 什么是迭代器? 可迭代对象通过__iter__()可以转换成迭代 ...

  2. layui(五)——form组件常见用法总结

    form 是我们非常看重的一块.layui中的form实现全自动的初始渲染,和基于事件驱动的接口书写方式.我整理了layui中form的配置.下边直接给一个栗子,后台采用.net MVC,除了razo ...

  3. C#数据结构学习

    Collection类学习 using System; using System.Collections.Generic; using System.Linq; using System.Text; ...

  4. 060、在docker中使用flannel(2019-03-29 周五)

    参考https://www.cnblogs.com/CloudMan6/p/7441188.html   配置docker 连接flannel   编辑host1的docker配置文件/etc/sys ...

  5. 自学python 5.

    1.tu = ("alex", [11, 22, {"k1": 'v1', "k2": ["age", "na ...

  6. int、bool和str

    int bit_length 返回以二进制表示的最短长度 print(int.bit_length(10)) 结果 4 Process finished with exit code 0 int() ...

  7. linux 中 如何 搜索 指定目录 下 指定文件 的 指定内容

    开发时,经常遇到 全局查找某些代码 linux 中 如何 检索 某 目录下指定文件 的 指定内容如下: //.点为查找当前目录 下 的 所有 *.php 文件里 有 hello 的文件 find . ...

  8. 【noip 2014】提高组Day2T3.华容道

    Description 小 B 最近迷上了华容道,可是他总是要花很长的时间才能完成一次.于是,他想到用编程来完成华容道:给定一种局面,华容道是否根本就无法完成,如果能完成,最少需要多少时间. 小 B ...

  9. Js JSON.stringify()与JSON.parse()与eval()详解及使用案例

    (1)JSON.parse函数 作用:将json字符串转换成json对象. 语法:JSON. parse(text[,reviver]). 参数:text  必须:一个有效的json字符串. revi ...

  10. visual studio code运行时报错,无法将“cnpm”项识别为 cmdlet、函数、脚本文件或可运行程序的名称,Cannot find module 'webpack'

    前言 因公司技术需求,这段时间成功进入了Vue 2.0 的坑,刚用起Visual Studio Code,却发现问题很多,发现一个错误:cnpm : 无法将“cnpm”项识别为 cmdlet.函数.脚 ...