yzoj P2371 爬山 题解

背景

其实 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;
}

原文地址:https://www.cnblogs.com/donkey2603089141/p/11632093.html

时间: 2024-10-15 19:35:25

yzoj P2371 爬山 题解的相关文章

小猫爬山题解(暴力解题)

[问题描述] Freda 和 rainbow 饲养了 N 只小猫,这天,小猫们要去爬山.经历了千辛万苦,小猫们 终于爬上了山顶,但是疲倦的它们再也不想徒步走下山了. Freda 和 rainbow 只好花钱让它们坐索道下山.索道上的缆车最大承重量为W,而N 只 小猫的重量分别是C1.C2……CN.当然,每辆缆车上的小猫的重量之和不能超过W.每租 用一辆缆车,Freda 和 rainbow 就要付 1 美元,所以他们想知道,最少需要付多少美元才能 把这 N 只小猫都运送下山? [输入格式] 第一行

yzoj P1122 阶乘 题解

T组数据,给出N,求出N!最右边非零的数. 对于30%的数据,N <= 30,T<=10. 对于全部的数据,N <= 10^2009,T<=30. 一道数学题 解析 N!/(10^x)最后一位数字即是结果.10^x进行拆分,变成5^x* 2^x.怎么除以5^x呢,好办,乘的时候含有5的倍数的一项全部不乘进去,再递归此过程.即 1 2 3 4 (15) 6 7 8 9 (25) 11 12 13 14 (35)16 17 18 19 (45) 21 22 23 24 (55) 26

yzoj P2345 战争 题解

纯数论 30分:纯暴力,直接模拟判断t秒后,判断hp是否小于0 60分: atk>=h,就是一炮一个,那么军队会在min(n,t)秒之后停止攻击,那么总伤害就是a[n+(n-1) +(n-2)+........(n-min(n,t)+1) 等差数列求和d=a(2n-min(n,t)+1)*(min(n,t))/2; 如果d>=hp Yes,否则No: 100分 考虑前t秒军队伤害总和,然后和hp比较大小 先算几下打死一个士兵,设m下 那么前m秒伤害为nma m+1到2m秒伤害为m(n-1)a

[USACO07OCT]障碍路线 &amp; yzoj P1130 拐弯 题解

题意 给出n* n 的图,A为起点,B为终点,* 为障碍,.可以行走,问最少需要拐90度的弯多少次,无法到达输出-1. 解析 思路:构造N * M * 4个点,即将原图的每个点分裂成4个点.其中点(i,j,k)表示在(i,j)时人的方向是k,然后对于两个点(i,j,k)和(i,j,kk),如果k和kk是两个旋转90度能转换的方向,就连一条边权为1的边,而对于(i,j,k)和(i+dx[ k],j+dy[k],k)连一条边权为0的边,表示从(i,j)在方向为k的情况下能向k方向走一步到达(i+dx

【题解】桐桐的爬山计划

题目描述 桐桐一直有个梦想,很希望像“蜘蛛人”罗伯特一样飞檐走壁.为了达成这个梦想,桐桐每天都辛勤练习攀爬.练习的出发点与终点都是在地上面.给出一个数列,代表她每次移动的距离.这个移动可以向上,也可以向下.但是不可能到达地下面去的.而她做练习使用的建筑物总是比她到达过的最高位置高2米.现在我们希望这个建筑物的高度越小越好. 如:20 20 20 20 如果是上,上,下,下的话,这个建筑物就要42米高,如果是上,下,上,下,就只要22米高. 当然有些数列是无解的,例如:3 4 2 1 6 4 5.

yzoj P2344 斯卡布罗集市 题解

共t条街对于每一条街上有n个店铺(n可能不相同),每次只能选两端第一个营业的店铺采购,采购第i个店铺会获得幸福度ai,采购完后,这个店铺和它相邻的店铺便会关门,问最大幸福度? 考场想了一下dp,一开始想一维但发现不好处理,二维参数也没有想出来,于是便开始了我的暴力瞎搞之旅,我随手写了几个例子发现对于n为奇数无论怎么采购,幸福度是固定的为a1, a3 , a5........an于是便可以直接累加,然而对于n为偶数又怎么去处理呢?我也随手写了n为6,8,10的数据发现其实就只有三种情况. 第一种情

yzoj P2349 取数 题解

题意 1到n个自然数中选k个自然数要求两两不相邻,问有多少种方法,模m eg(1 3 5 ) 又是一道打表规律题,正常解法dp可以通过前缀和优化到O(N* K).另外我们可以重新定义F[I,J]表示从1到I中选择J个不连续数的方案数.通过考虑I选还是不选来进行状态转移. 1.如果不选I:则方案数为F[I-1,J]; 2.如果选I:由于不能选相邻两个数,所以I-1不能选,则剩余的J-1个数只能在1到I-2中选,即F[I-2,J-1]; f[i][j]=f[i-1][j]+f[i-2][j-1] (

yzoj 2372 小B的数字 题解

题意 判断是否存在一个序列 $ b_i $ 使得 $ \prod_{i = 1}^{n} b_i ?| b_i^{a_i}$ 恒成立,其中 $ b_i $ 中的每个数都是2的正整数次幂. 样例输入 3 2 3 2 3 3 3 3 2 1 10 样例输出 YES YES NO 数据范围 对于 100% 的数据有 $ n \leq 10^5,a_i \leq 10,T \leq 10$ 解析 首先拿到这道题,考场一看就知道不是规律题就是数学公式题,事实上是的. 我们可以设 $ b_i=2^{x_i}

TyvjP2018 「Nescaf&#233;26」小猫爬山

P2018 「Nescafé26」小猫爬山 时间: 1000ms / 空间: 131072KiB / Java类名: Main 背景 Freda和rainbow饲养了N只小猫,这天,小猫们要去爬山.经历了千辛万苦,小猫们终于爬上了山顶,但是疲倦的它们再也不想徒步走下山了(呜咕>_<). 描述 Freda和rainbow只好花钱让它们坐索道下山.索道上的缆车最大承重量为W,而N只小猫的重量分别是C1.C2……CN.当然,每辆缆车上的小猫的重量之和不能超过W.每租用一辆缆车,Freda和rainb