背景

其实 Kano 曾经到过由乃⼭,当然这名字⼀看⼭主就是 Yuno 嘛。当年 Kano 看见了由乃⼭,内⼼突然涌出了⼀股杜甫会当凌绝顶,⼀览众⼭⼩的 豪⽓,于是毅然决定登⼭。但是 Kano 总是习惯性乱丢垃圾,增重环卫⼯⼈ 的负担,Yuno 并不想让 Kano 登⼭,于是她果断在⼭上设置了结界……

题意

Yuno 为了⽅便登⼭者,在⼭上造了 N 个营地,编号从 0 开始。当结界发动时,每当第 $ i(> 0) $ 号营地内有⼈,那么他将被传送到第 $ A_i (< i) $ 号营 地,如此循环,所以显然最后只会被传送到第 0 号营地。

但 Kano 并不知晓结界的情况。他登⼭的⽅法是这样的:⾸先分⾝出⼀个编号为$ D_i $ 的 Kano,然后将其⽤投⽯机抛掷到营地 $ G_i $ 。Kano 总共做了 M 次这样的登⼭操作,但每次抛出去的 Kano 都被传送回了营地 0,所以 Kano 只好放弃了。

但是 Kano 在思考⼀个问题,到底每个营地被多少只编号不同的 Kano 经过过?

输入

第⼀⾏两个整数 $ N,M,$ 表⽰⼭的营地数和登⼭次数。

接下来 $ N − 1 $ ⾏,每⾏⼀个数,第 $ i $ ⾏为 $ A_i $ ,表⽰营地 i 将会传向营 地 $ A_i $ 。

接下来 $ M $ ⾏,每⾏两个数 $ G_i ,D_i $ 。

输出

共 $ N $ ⾏,每⾏表⽰营地 $ i $ 有多少不同编号的 Kano 曾经通过。

数据范围

$ N,M<=10^5, max(D_i) <=10^9 $

看完题考场临时打的暴力,才10分,同机房的大佬暴力90分。。。我菜爆惹

解析

算法一

开一个数组 $ a[i, j] \(表示第\) i $个点的第 $ j \(种标记是否被打过,每次询问可以\) O(total) \(回答,其中\) total $是标记数。

或使用一个计数器,每次有某个 $ a[i, j] $ 从 $ False $ 变 $ True $ 则 $ i $的计数器加1,询问可以做到O(1)。

每次修改暴力打标记。明显的,如果 $ max(D_i) $ 大于 $ M \(,则可以通过离散把数量级下降到\) M $。

时间复杂度$ O(NM)$,空间复杂度 $ O(Nmax(D_i)) $

算法二

由于每次修改影响到的仅是修改点的祖先,可以换一种储存方式,直接把标记打到点上。每次询问一个点只需要查询该点为根的子树下有多少不同的标记。

这样可以使用DFS序,对每种标记开一个树状数组,对每个标记,查询该子树下是否有标记

时间复杂度 $ O(M log(N)+N max(D_i)log(N)) $,空间复杂度 $ O(N max(D_i)) $

算法三

发现算法二没有必要使用树状数组维护,只需要每种标记分开处理,处理只需要简单地遍历整棵树

时间复杂度 $ O(N max(D_i)) $ , 空间复杂度 $ O(N)$.

算法四

首先我们仅考虑只有一种标记的情况,如果仅有一种标记,那么我们可以把被打标记的点进行+1操作,那么如果最后,一个点的子树和大于 $ 1 $,该点就是有标记的。

但是这样处理不了多种标记的数量,所以要把多余的 $ +1 $ 标记减去,而多余的 $ +1 $ 标记的形成是两个操作拥有公共祖先,所以在两个操作点的 $ LCA $ 处打上一个 $ -1 $ 标记就可避免多加的情况,原来的两个操作也就变成了一个操作了,可以证明-1标记只会打 $ M $ 个。

如此就可以拓展到多种标记,⽽标记数就是该点的⼦树权值和。为了方便打 $ -1 $ 标记,我们可以交换操作顺序,把同⼀种标记的⼀起做,而 $ -1 $ 标记所打的点就是这些相同标记的操作点按 $ DFS $ 序排序之后相邻两点的 $ LCA $ 。

时间复杂度为 $ O(MlogN + N + MlogM) $

ac代码

传送到哪个点便与它建一条边,将其作为父节点,显然 $ 0 $ 为根节点,于是我们只要对分身从小到大进行排序($ D_i $为第一关键字, $ dfs $ 序为第二关键字),每次枚举 $ D_i $ 相同 $ G_i $ 不同的相邻点对其 $ lca $ 打上 $ -1 $ 标记,其余打上 $ +1 $ 标记,最后跑一遍 $ dfs $ 将每个点的子树的值统计起来就是答案。

#include<bits/stdc++.h>
using namespace std;
const int size=200010;
struct node{
int g;//地点
int d;//编号
}b[size];
int f[size][20],d[size],book[size],st[size],a[size],times;
int ver[2*size],v[size],Next[2*size],head[size];
int m,n,tot,t,ans;
int x,y,z;
queue<int> q;
void add(int x,int y){//建树
ver[++tot]=y;Next[tot]=head[x];head[x]=tot;
}
void dfs(int x){//确定dfs序
v[x]=1;
st[x]=++times;
for(int i=head[x];i;i=Next[i]){
int y=ver[i];
if(v[y]) continue;
dfs(y);
}
}
void bfs(){//lca的预处理
q.push(0);d[0]=1;
while(q.size()){
int x=q.front();q.pop();
for(int i=head[x];i;i=Next[i]){
int y=ver[i];
if(d[y]) continue;
d[y]=d[x]+1;
f[y][0]=x;
for(int j=1;j<=t;++j){
f[y][j]=f[f[y][j-1]][j-1];
}
q.push(y);
}
}
}
bool cmp(node a,node b){//排序
if(a.d==b.d) return st[a.g]<st[b.g];
else return a.d<b.d;
}
int lca(int x,int y){//lca
if(d[x]>d[y]) swap(x,y);
for(int i=t;i>=0;--i){
if(d[f[y][i]]>=d[x]){
y=f[y][i];
}
}
if(x==y) return x;
for(int i=t;i>=0;--i){
if(f[x][i]!=f[y][i]){
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];
}
void find(int x){//计算答案
v[x]=1;
for(int i=head[x];i;i=Next[i]){
int y=ver[i];
if(v[y]) continue;
find(y);
book[x]+=book[y];
}
}
int main(){
scanf("%d %d",&n,&m);
t=((int)(log(n)/log(2)))+1;
for(int i=1;i<n;++i){//建边
scanf("%d",&a[i]);
add(a[i],i);
add(i,a[i]);
}
for(int i=1;i<=m;++i){
scanf("%d %d",&b[i].g,&b[i].d);
}
dfs(0);
bfs();
sort(b+1,b+1+m,cmp);//按照编号排序,其次按dfs序排序
int pre=-1;//当前点的上一个
for(int i=1;i<=m;++i){//核心代码
int x=b[i].g;
book[x]++;
if(pre!=-1) book[lca(pre,x)]--;//如果编号相同并且地点不同就减去lca
pre=x;
if(i<m&&b[i].d!=b[i+1].d)pre=-1;//判断地点是否相同
}
memset(v,0,sizeof(v));
find(0);
for(int i=0;i<n;++i){
printf("%d\n",book[i]);
}
return 0;
}

同机房一位大佬的ac代码(暴力优化跑过!)

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std; int n,m,arr[100010],ans[100010],sum[100010],tmp;
int pd[100010];
struct dat{
int d,g;
};
dat mov[100010];
bool cmp(dat a,dat b){
if(a.d!=b.d) return a.d<b.d;
return a.g>b.g;
}
int main(){
scanf("%d%d",&n,&m);
--n;
for(int i=1;i<=n;++i) scanf("%d",&arr[i]);
for(int i=1;i<=m;++i) scanf("%d",&mov[i].g),scanf("%d",&mov[i].d);
sort(mov+1,mov+1+m,cmp);
++m;
for(register int i=1;i<=m;++i){
if(mov[i].d!=mov[i-1].d){
if(mov[i].d!=mov[i+1].d){
sum[mov[i].g]++;
continue;
}
++ans[0];
}
tmp=mov[i].g;
while(tmp){
if(pd[tmp]==mov[i].d) break;
pd[tmp]=mov[i].d;
++ans[tmp];
tmp=arr[tmp];
}
}
--ans[0];
for(int i=n;i>=1;--i){
sum[arr[i]]+=sum[i];
ans[i]+=sum[i];
}
ans[0]+=sum[0];
for(int i=0;i<=n;++i) printf("%d\n",ans[i]);
return 0;
}

yzoj P2371 爬山 题解的更多相关文章

  1. yzoj P1126 塔 题解

    题意:给n个积木,搭成两个高度相同的塔,问最高高度 正解是dp 答案在dp[n][0] 代码 #include<bits/stdc++.h> using namespace std; in ...

  2. 洛谷P1661 & yzoj 1650 扩散 题解

    题意 先讲一下一种容易陷入误区错误思路 要使时间最小,就去找相对于每个点的最短曼哈顿距离,然后取最大值,时间就是(maxn+1)/2. 代码 #include<cstring> #incl ...

  3. [USACO07OCT]障碍路线 & yzoj P1130 拐弯 题解

    题意 给出n* n 的图,A为起点,B为终点,* 为障碍,.可以行走,问最少需要拐90度的弯多少次,无法到达输出-1. 解析 思路:构造N * M * 4个点,即将原图的每个点分裂成4个点.其中点(i ...

  4. yzoj P1122 阶乘 题解

    T组数据,给出N,求出N!最右边非零的数. 对于30%的数据,N <= 30,T<=10. 对于全部的数据,N <= 10^2009,T<=30. 一道数学题 解析 N!/(1 ...

  5. yzoj P2345 战争 题解

    纯数论 30分:纯暴力,直接模拟判断t秒后,判断hp是否小于0 60分: atk>=h,就是一炮一个,那么军队会在min(n,t)秒之后停止攻击,那么总伤害就是a[n+(n-1) +(n-2)+ ...

  6. AcWing P165 小猫爬山 题解

    Analysis 这道题是搜索,类似于小木棍,加一些剪枝. 第一个剪枝是如果当前的答案已经大于了我们已知的最小答案,不用说直接return返回即可. 第二个剪枝是我们可以将小猫的体重从大到小排序,这样 ...

  7. NOIP模拟测试29「爬山·学数数·七十和十七」

    爬山题解不想写了 学数数 离散化然后找到以每一个值为最大值的连续子段有多少个,然后开个桶维护 那么怎么找以每一个值为最大值的连续子段个数 方法1(我的极笨的方法) 考试时我的丑陋思路, 定义极左值为左 ...

  8. yzoj P2043 & 洛谷 P1282 多米诺骨牌 题解

    题意 类似于就是背包. 解析 代码 跟解析有点不一样v[i]价值,w[i]重量,s背包容积,背包转移即可. #include<bits/stdc++.h> using namespace ...

  9. yzoj P2044 数字游戏 题解

    题意 dfs骗了30分,一开始想的距离正解差一点啊,贪心加dp就可以过的水题,真正太蒻了 解析 代码 #include<bits/stdc++.h> using namespace std ...

随机推荐

  1. 洛谷 P2341 【受欢迎的牛】

    题库:洛谷 题号:2341 题目:受欢迎的牛 link:https://www.luogu.org/problemnew/show/P2341 思路:因为奶牛的爱慕关系具有传递性,所以每个环(强连通分 ...

  2. FEL,项目实装记录

    FEL,即Fast EL ,版本0.8,具体内容我就不贴了,自行百度 实装遇到的问题: Spring Boot 打包后无法进行表达式编译. 根据百度以及源码,确定这东西是在用JavaCompiler将 ...

  3. HDU-6356 Glad You Came 线段树 ST表

    HDU-6356 题意:有m次操作,每次操作通过给定的随机函数生成 l , r , v,使得在 l 到 r 区间内,所有的a[i]变为max(a[i] , v). 最后输出n个a[i]* i的异或和. ...

  4. Service:让客户端发现pod并与之通信

    5.1.Service介绍 5.1.1.Serice简介 5.1.1.1什么是Service service是k8s中的一个重要概念,主要是提供负载均衡和服务自动发现. Service 是由 kube ...

  5. Node基础-CommonJS模块化规范

    1.在本地项目中基于NPM/YARN安装第三方模块 第一步:在本地项目中创建一个"package.json"的文件 作用:把当前项目所有依赖的第三方模块信息(包含:模块名称以及版本 ...

  6. 封装axios来管控api的2种方式

    前言:我们在开发项目的时候,往往要处理大量的接口.并且在测试环境 开发环境 生产环境使用的接口baseurl都不一样 这时候如果在开发环境完成之后切换每一个接口的baseurl会变的非常的麻烦,(要去 ...

  7. 《即时消息技术剖析与实战》学习笔记5——IM系统如何保证消息的一致性

    一.什么是消息一致性 消息一致性指的是消息的时序一致性,即消息收发的一致性.如果不能保证时序一致性,就会造成聊天语义不连贯,引起误会. 对于点对点的聊天场景,时序一致性保证接收方的接收顺序和发送方的发 ...

  8. Python网络爬虫实战(一)快速入门

    本系列从零开始阐述如何编写Python网络爬虫,以及网络爬虫中容易遇到的问题,比如具有反爬,加密的网站,还有爬虫拿不到数据,以及登录验证等问题,会伴随大量网站的爬虫实战来进行. 我们编写网络爬虫最主要 ...

  9. [币严区块链]数字货币交易所之瑞波(XRP)钱包对接

    对接Ripple(XRP),不需要本地部署钱包,直接访问Ripple API,本文包括访问Ripple API及如何免费获取测试的XRP. 对接流程 安装Ripple API Ripple API 接 ...

  10. 警告:Using platform encoding (GBK actually) to copy filtered resources, i.e. build is platform dependent!

    执行Maven Install打包的时候,提示以下警告信息: [WARNING] Using platform encoding (GBK actually) to copy filtered res ...