题目


最近比较懒,题目描述都直接截图了。

题目大意

给你一棵树,还有树上的几条路径,一条路径上的点到路径上其它任意点的代价为111。然后是一堆询问,问从一个点到另一个点的最小代价。


思路

一开始做这题时,就自然地往链剖或倍增方面想。
链剖想不出,于是就想倍增。
对于每个点,预处理出它花111的代价能到达的最远祖先。
然后倍增一下~
询问的时候,就将两个点的LCALCALCA求出,然后两个点往上跳,直到再跳就超过LCALCALCA为止。
所以问题就转化成了求两个点是否被一条路径直接连通,如果是,就加111,否则加222。
想不出来……
最后只能打暴力:将一个点作为根,然后粗暴地处理……


正解

事实证明我前面所思考的是正确的。
对于这个问题,其实可以转化成:是否有一条路径的两个端点分别在这两个点的子树内……
好像是很显然的,可我为什么想不到……
于是我们就可以求出它们的dfndfndfn序,这就成了一个平面上的问题。
每一条路径可以看做一个点,每一个询问可以看作一个矩形,求这个矩形当中是否有点出现。
然后就是一个简单的扫描线和树状数组了。


代码

有一点需要补充一下:
有的同学反映,这题递归会爆栈,所以要打人工栈。
反正我是没有爆栈……
后来听说xzb大爷想出一个不用dfs求dfn序的方法。
首先,我们可以通过拓扑排序之类的自底向上递推求出它们的siz等信息。(这题比较良心,由于每个点的父亲编号小于它,所以直接从后往前扫就好了。)
然后自顶向下(可以用bfs,这题直接从前往后扫就好了),
对于每个点分两种情况:

  1. 它是父亲的第一个儿子。那么它的dfndfndfn即为父亲加一。
  2. 否则,就是父亲上一个儿子的dfndfndfn加它的sizsizsiz。

然后就可以简单地求出dfndfndfn了。
正常的bfs中,上一个点就是父亲的上一个儿子。
不过这题不用bfs,直接记录就好了。
在此膜拜xzb

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 200000
#define M N
#define Q N
int n;
int last[N+1];
int siz[N+1],dfn[N+1];
int fa[N+1][19],dep[N+1];
inline void get_dfn(){//这是一种不需要dfs求dfn的方法,解释见上dfn[1]=1;for (int i=2;i<=n;++i){if (last[fa[i][0]])dfn[i]=dfn[last[fa[i][0]]]+siz[last[fa[i][0]]];elsedfn[i]=dfn[fa[i][0]]+1;last[fa[i][0]]=i;}
}
inline int LCA(int u,int v){if (dep[u]<dep[v])swap(u,v);for (int k=dep[u]-dep[v],i=0;k;k>>=1,++i)if (k&1)u=fa[u][i];if (u==v)return u;for (int i=17;i>=0;--i)if (fa[u][i]!=fa[v][i]){u=fa[u][i];v=fa[v][i];}return fa[u][0];
}
int m;
int up[N+1][19],dep2[N+1];
struct Oper{int x,l,r;int ty;int num;
} o[M*2+Q*2+1];
int cnt;
bool cmp(const Oper &a,const Oper &b){return a.x<b.x || a.x==b.x && a.ty<b.ty;
}
int t[N+1];
#define lowbit(x) ((x)&(-x))
inline void change(int x){do{t[x]++;x+=lowbit(x);}while (x<=n);
}
inline int query(int x){int res=0;while (x){res+=t[x];x-=lowbit(x);}return res;
}
int ans[Q+1],cha[Q+1];
int main(){freopen("car.in","r",stdin);freopen("car.out","w",stdout);scanf("%d",&n);for (int i=2;i<=n;++i){scanf("%d",&fa[i][0]);e[++ne]={i,last[fa[i][0]]};last[fa[i][0]]=e+ne;}for (int i=1;i<=n;++i)dep[i]=dep[fa[i][0]]+1;//因为题目说每个点父亲的编号一定小于它,所以直接递推就好,下同。for (int i=n;i>=1;--i)siz[fa[i][0]]+=++siz[i];get_dfn(1);for (int i=1;i<=17;++i)for (int j=1;j<=n;++j)fa[j][i]=fa[fa[j][i-1]][i-1];//倍增处理for (int i=1;i<=n;++i)up[i][0]=i;//up[i][j]表示花费2^j的代价最远能到达的祖先scanf("%d",&m);for (int i=1;i<=m;++i){int u,v;scanf("%d%d",&u,&v);if (dfn[u]>dfn[v])swap(u,v);//处理花费1代价能到达的最远祖先int lca=LCA(u,v);if (dep[lca]<dep[up[u][0]])up[u][0]=lca;if (dep[lca]<dep[up[v][0]])up[v][0]=lca;//加入操作列表中,相当于点(因为它有对称性,所以正着反着都加进去)o[++cnt]={dfn[u],dfn[v],dfn[v],0,0};o[++cnt]={dfn[v],dfn[u],dfn[u],0,0};}for (int i=n;i>=1;--i)if (dep[up[i][0]]<dep[up[fa[i][0]][0]])up[fa[i][0]][0]=up[i][0];//从底向上递推出每个点花费1代价到达的最远祖先for (int i=1;i<=18;++i)for (int j=1;j<=n;++j)up[j][i]=up[up[j][i-1]][i-1];int q;scanf("%d",&q);for (int i=1;i<=q;++i){int u,v;scanf("%d%d",&u,&v);if (u==v)continue;if (up[u][18]!=up[v][18]){//判断两个点是否可以互相到达(因为N=200000,2^18>N,所以这是它能到达的最远祖先,因为这是一棵树,所以如果可达到的最远祖先不一样,那么他们就不能互相到达)ans[i]=-1;continue;}int lca=LCA(u,v);if (lca==u || lca==v){//在同一条链上的情况if (lca==u)swap(u,v);for (int j=17;j>=0;--j)if (dep[up[u][j]]>dep[v])u=up[u][j],ans[i]+=1<<j;//跳到LCA下的最远点ans[i]++;continue;}for (int j=17;j>=0;--j)if (dep[up[u][j]]>dep[lca])u=up[u][j],ans[i]+=1<<j;//跳到LCA下的最远点,下同for (int j=17;j>=0;--j)if (dep[up[v][j]]>dep[lca])v=up[v][j],ans[i]+=1<<j;//加入操作o[++cnt]={dfn[u]-1,dfn[v],dfn[v]+siz[v]-1,1,i};o[++cnt]={dfn[u]+siz[u],dfn[v],dfn[v]+siz[v]-1,-1,i};}sort(o+1,o+cnt+1,cmp);for (int i=1;i<=cnt;++i)//扫描线求出二维数点问题if (o[i].ty==0)change(o[i].l);else if (o[i].ty==1)cha[o[i].num]=query(o[i].r)-query(o[i].l-1);elseans[o[i].num]+=((query(o[i].r)-query(o[i].l-1))-cha[o[i].num]?1:2);for (int i=1;i<=q;++i)printf("%d\n",ans[i]);return 0;
}

总结

以后,看见有关树的题目,如果普通的倍增、链剖都做不出来,就得要往dfndfndfn序方面想……

转载于:https://www.cnblogs.com/jz-597/p/11145263.html

JZOJ5918【NOIP2018模拟10.20】Car相关推荐

  1. 备战Noip2018模拟赛20 (A组) T1 Cz 礼物

    10月24日备战Noip2018模拟赛20(A组) T1 Cz礼物 题目描述 有Ñ种物品,第I种物品的价格为六,每天最多购买XI个. 有米天,第我天c♂x有无线的钱,他会不停购买能买得起的最贵的物品( ...

  2. jzoj5904. 【NOIP2018模拟10.15】刺客信条(并查集)

    5904. [NOIP2018模拟10.15]刺客信条 Description 故事发生在1486 年的意大利,Ezio 原本只是一个文艺复兴时期的贵族,后来因为家族成员受到圣殿骑士的杀害,决心成为一 ...

  3. jzoj 5906. 【NOIP2018模拟10.15】传送门(树形dp)

    5906. [NOIP2018模拟10.15]传送门 Description 8102年,Normalgod在GLaDOS的帮助下,研制出了传送枪.但GLaDOS想把传送枪据为己有,于是把Normal ...

  4. jzoj5920. 【NOIP2018模拟10.21】风筝(dp,最长上升子序列)

    5920. [NOIP2018模拟10.21]风筝 Description 当一阵风吹来,风筝飞上天空,为了你,而祈祷,而祝福,而感动-- Description oyiya 在 AK 了 IOI 之 ...

  5. JZOJ 5922. 【NOIP2018模拟10.23】sequence

    Description 小 F 是一位 Hack 国的居民,他生活在一条长度为 n 的街道上,这个街道上总共有 n 个商店.每个商店里售卖着不同的 Hack 技能包,每个商店本身也会有个便利值.初始时 ...

  6. JZOJ 5904. 【NOIP2018模拟10.15】刺客信条(AC)

    Description 故事发生在1486 年的意大利,Ezio 原本只是一个文艺复兴时期的贵族,后来因为家族成员受到圣殿骑士的杀害,决心成为一名刺客.最终,凭借着他的努力和出众的天赋,成为了杰出的刺 ...

  7. JZOJ-senior-5937. 【NOIP2018模拟10.30】斩杀计划

    Time Limits: 1000 ms Memory Limits: 262144 KB Description 众所周知,小J和小G是死对头,一天小G带领一群小弟找到了小J. 小G有n个小弟,第i ...

  8. jzoj 5904. 【NOIP2018模拟10.15】刺客信条 二分+并查集

    刺客信条 时间限制: 1 Sec 内存限制: 512 MB 题目描述 故事发生在1486 年的意大利,Ezio 原本只是一个文艺复兴时期的贵族,后来因为家族成员受到圣殿骑士的杀害, 决心成为一名刺客. ...

  9. JZOJ 5904【NOIP2018模拟10.15】刺客信条

    昨晚改出来了 被墙联通坑死了 手改大数据...... 先放题目 Description 故事发生在1486 年的意大利,Ezio 原本只是一个文艺复兴时期的贵族,后来因为家族成员受到圣殿骑士的杀害,决 ...

最新文章

  1. Python学习笔记之五:类定义
  2. C#——事件(Event)DEMO[闻鸡起舞]
  3. C语言查找单链列表的第k个元素的算法(附完整源码)
  4. 51nod 1575 Gcd and Lcm
  5. mysql 二元分词_MySQL 中文分词原理
  6. 作者:陈维政,男,北京大学博士生。
  7. 支持向量机——深度AI科普团队
  8. Javascript实现页面跳转传值示例Demo
  9. javascript学习笔记之document对象、表单及表单元素、脚本化cookie
  10. 操作教程:摄像头通过GB28181协议注册EasyCVR的详细配置
  11. 前端实现鼠标拖拽功能
  12. 动态规划练习(1)--[编程题] 风口的猪-中国牛市
  13. [Builder]代码中android版本的判断[from oschina.亭子happy]
  14. 认识物联网系列——物联网架构
  15. JS原生---歌词滚动效果案例
  16. 怎么用notepad将html格式化,Notepad++如何使用Tidy2格式化HTML文档?
  17. 淘宝API获取——商品详情信息、DESC信息、主图
  18. Mentor PADS铺铜指示器的关闭操作
  19. Liunx之网络编程
  20. #(三)、股市中无处不在的随机性

热门文章

  1. linux 通过at命令创建任务
  2. 美团点评2017秋招笔试编程题
  3. 查找数组连成环形的和最大的连续子数组
  4. FZU 2124 吃豆人 bfs
  5. 使用TFHelp解析Html
  6. php获取网页内容方法总结
  7. 防止病毒迷惑了您的眼睛
  8. 在Project中引用zedgraph控件
  9. Facebook AI 提出10亿级数据规模的半监督图像分类模型,ImageNet测试精度高达81.2%!...
  10. 思考问题:Python这两段等效代码内存占用为什么差别那么大?