洛谷 P3258 [JLOI2014]松鼠的新家 树链剖分+差分前缀和优化
题面
题目链接
题目描述
松鼠的新家是一棵树,前几天刚刚装修了新家,新家有 $ n $ 个房间,并且有 $ n-1 $ 根树枝连接,每个房间都可以相互到达,且俩个房间之间的路线都是唯一的。天哪,他居然真的住在”树“上。
松鼠想邀请小熊维尼前来参观,并且还指定一份参观指南,他希望维尼能够按照他的指南顺序,先去 $ a_1 $,再去 $ a_2 $,......,最后到 $ a_n $,去参观新家。可是这样会导致维尼重复走很多房间,懒惰的维尼不停地推辞。可是松鼠告诉他,每走到一个房间,他就可以从房间拿一块糖果吃。
维尼是个馋家伙,立马就答应了。现在松鼠希望知道为了保证维尼有糖果吃,他需要在每一个房间各放至少多少个糖果。
因为松鼠参观指南上的最后一个房间 $ a_n $ 是餐厅,餐厅里他准备了丰盛的大餐,所以当维尼在参观的最后到达餐厅时就不需要再拿糖果吃了。
输入输出格式
输入格式
第一行一个整数 $ n $,表示房间个数第二行n个整数,依次描述 $ a_1-a_n $
接下来 $ n-1 $ 行,每行两个整数 $ x $ , $ y $ ,表示标号x和y的两个房间之间有树枝相连。
输出格式
一共 $ n $ 行,第 $ i $ 行输出标号为i的房间至少需要放多少个糖果,才能让维尼有糖果吃。
输入输出样例
输入样例:
5
1 4 5 3 2
1 2
2 4
2 3
4 5
输出样例:
1
2
1
2
1
说明
【数据范围】
$ 2 \leq n \leq 300000 $
说明
【时空限制】
1000ms,128M
思路
整理题意,可参考如下理解:
给定一个n棵结点的树,一个人从给定的起点出发按一定的顺序走至终点;他经过每个点时,该点点权加1 (包括起点但不包括终点)。求最终每点的点权。
首先我考虑的是每次改变a[i]和a[i+1]之间所有点的点权。可以拿纸模拟一下,会发现这样的问题。
这样计算,那么样例得到的结果最终为1 3 2 3 2
由于在a[i]到a[i+1]再到a[i+2]的过程中,按照上面的算法,先改变a[i]和a[i+1]之间所有点的点权,再改变a[i+1]和a[i+2]之间所有点的点权,那么a[i+1]的点权就重复算了一次。故除了起点和终点,其他每个点点权都多算了1,应该减去;而终点也多算了一次,原因是最后一次到达终点时点权不用加1;起点并没有重复,不用减去。
所以最终算法归纳为:
1.每次改变a[i]和a[i+1]之间所有点的点权,$ 1 \leq i < n $
2.将除起点外的所有点点权减1
这样基本上是一道树剖的板子题了吧
AC代码
#include<bits/stdc++.h>
const int maxn=300010;
using namespace std;
int n,a[maxn];
int tot,to[maxn<<1],nxt[maxn<<1],head[maxn];
int dep[maxn],fa[maxn],son[maxn],len[maxn];
int cnt,nid[maxn],nw[maxn],top[maxn];
struct SegmentTree
{
int l,r,tag,sum;
#define l(a) tree[a].l
#define r(a) tree[a].r
#define m(a) ((l(a)+r(a))>>1)
#define len(a) (r(a)-l(a)+1)
#define t(a) tree[a].tag
#define s(a) tree[a].sum
}tree[maxn<<2];
void dfs1(int u,int f,int d)
{
dep[u]=d;fa[u]=f;len[u]=1;
for(int i=head[u];i;i=nxt[i])
{
int v=to[i];
if(v==f) continue;
dfs1(v,u,d+1);
len[u]+=len[v];
if(len[v]>len[son[u]]) son[u]=v;
}
}
void dfs2(int p,int t)
{
nid[p]=++cnt;
top[p]=t;
if(!son[p]) return;
dfs2(son[p],t);
for(int i=head[p];i;i=nxt[i])
{
int v=to[i];
if(v==fa[p] || v==son[p]) continue;
dfs2(v,v);
}
}
void BuildTree(int p,int l,int r)
{
l(p)=l;r(p)=r;
if(l==r) return;
BuildTree(p<<1,l,m(p));
BuildTree(p<<1|1,m(p)+1,r);
}
void PushDown(int p)
{
if(t(p))
{
s(p<<1)+=t(p)*len(p<<1);
s(p<<1|1)+=t(p)*len(p<<1|1);
t(p<<1)+=t(p);
t(p<<1|1)+=t(p);
t(p)=0;
}
}
void Change1(int p,int l,int r)
{
if(l<=l(p) && r>=r(p))
{
t(p)++;
s(p)+=len(p);
return;
}
PushDown(p);
if(l<=m(p)) Change1(p<<1,l,r);
if(r>m(p)) Change1(p<<1|1,l,r);
s(p)=s(p<<1)+s(p<<1|1);
}
int Ask(int p,int l,int r)
{
if(l<=l(p) && r>=r(p)) return s(p);
PushDown(p);
int ans=0;
if(l<=m(p)) ans+=Ask(p<<1,l,r);
if(r>m(p)) ans+=Ask(p<<1|1,l,r);
return ans;
}
void Change2(int u,int v)
{
while(top[u]!=top[v])
{
if(dep[top[u]]<dep[top[v]]) swap(u,v);
Change1(1,nid[top[u]],nid[u]);
u=fa[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
Change1(1,nid[u],nid[v]);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<n;i++)
{
int u,v;scanf("%d%d",&u,&v);
to[++tot]=v;nxt[tot]=head[u];head[u]=tot;
to[++tot]=u;nxt[tot]=head[v];head[v]=tot;
}
dfs1(a[1],a[1],1);
dfs2(a[1],a[1]);
BuildTree(1,1,n);
for(int i=1;i<n;i++) Change2(a[i],a[i+1]);
for(int i=1;i<=n;i++) printf("%d\n",Ask(1,nid[i],nid[i])-(i==a[1]? 0:1));
return 0;
}
优化
然而,这个题的修改操作很简单,就是很多个区间上同时加1;而如果用线段树,还会存储一堆区间和,有点浪费。可以考虑差分前缀和的方式

如果b是a的差分数组,那么在a的某个[i,j]内同时加上1,等效于b[i]++,b[j+1]--;统计答案时只需要从左到右扫一遍就可以了
优化后AC代码
#include<bits/stdc++.h>
const int maxn=300010;
using namespace std;
int n,a[maxn];
int tot,to[maxn<<1],nxt[maxn<<1],head[maxn];
int dep[maxn],fa[maxn],son[maxn],len[maxn];
int cnt,nid[maxn],top[maxn];
int ans[maxn];
struct SegmentTree
{
int l,r,tag,sum;
#define l(a) tree[a].l
#define r(a) tree[a].r
#define m(a) ((l(a)+r(a))>>1)
#define len(a) (r(a)-l(a)+1)
#define t(a) tree[a].tag
#define s(a) tree[a].sum
}tree[maxn<<2];
void dfs1(int u,int f,int d)
{
dep[u]=d;fa[u]=f;len[u]=1;
for(int i=head[u];i;i=nxt[i])
{
int v=to[i];
if(v==f) continue;
dfs1(v,u,d+1);
len[u]+=len[v];
if(len[v]>len[son[u]]) son[u]=v;
}
}
void dfs2(int p,int t)
{
nid[p]=++cnt;
top[p]=t;
if(!son[p]) return;
dfs2(son[p],t);
for(int i=head[p];i;i=nxt[i])
{
int v=to[i];
if(v==fa[p] || v==son[p]) continue;
dfs2(v,v);
}
}
void Change1(int i,int j)
{
ans[i]++;ans[j+1]--;
}
void Change2(int u,int v)
{
while(top[u]!=top[v])
{
if(dep[top[u]]<dep[top[v]]) swap(u,v);
Change1(nid[top[u]],nid[u]);
u=fa[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
Change1(nid[u],nid[v]);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<n;i++)
{
int u,v;scanf("%d%d",&u,&v);
to[++tot]=v;nxt[tot]=head[u];head[u]=tot;
to[++tot]=u;nxt[tot]=head[v];head[v]=tot;
}
dfs1(a[1],a[1],1);
dfs2(a[1],a[1]);
for(int i=1;i<n;i++) Change2(a[i],a[i+1]);
for(int i=2;i<=n;i++) ans[i]+=ans[i-1];
for(int i=1;i<=n;i++) printf("%d\n",ans[nid[i]]-(i==a[1]? 0:1)); ///判断是否是起点,如果不是起点则-1
return 0;
}
总结
区间加1可以用差分,这样仅仅只用更改两个端点
洛谷 P3258 [JLOI2014]松鼠的新家 树链剖分+差分前缀和优化的更多相关文章
- 洛谷 P3258 [JLOI2014]松鼠的新家(树链剖分)
题目描述松鼠的新家是一棵树,前几天刚刚装修了新家,新家有n个房间,并且有n-1根树枝连接,每个房间都可以相互到达,且俩个房间之间的路线都是唯一的.天哪,他居然真的住在”树“上. 松鼠想邀请小熊维尼前来 ...
- P3258 [JLOI2014]松鼠的新家 树链剖分
这个题就是一道树剖板子题,就是每走一步就把所有的经过点加一就行了.还有,我的树剖板子没问题!!!谁知道为什么板子T3个点!我不管了!反正这道题正常写A了. 题干: 题目描述 松鼠的新家是一棵树,前几天 ...
- 洛谷P3258 [JLOI2014]松鼠的新家【LCA+树上差分】
简要题意 树上n个节点,给定路径,求每个点经过次数 题意分析 对于每两个点,有两种情况,第一种,他们的lca为本身,第二种,他们有公共祖先,又要求他们的点经过次数,暴力是不可能的,复杂度不对,所以可以 ...
- 洛谷 P3258 [JLOI2014]松鼠的新家 解题报告
P3258 [JLOI2014]松鼠的新家 题目描述 松鼠的新家是一棵树,前几天刚刚装修了新家,新家有n个房间,并且有n-1根树枝连接,每个房间都可以相互到达,且俩个房间之间的路线都是唯一的.天哪,他 ...
- 洛谷P3258 [JLOI2014]松鼠的新家
P3258 [JLOI2014]松鼠的新家 题目描述 松鼠的新家是一棵树,前几天刚刚装修了新家,新家有n个房间,并且有n-1根树枝连接,每个房间都可以相互到达,且俩个房间之间的路线都是唯一的.天哪,他 ...
- 洛谷 P3258 [JLOI2014]松鼠的新家 题解
P3258 [JLOI2014]松鼠的新家 题目描述 松鼠的新家是一棵树,前几天刚刚装修了新家,新家有n个房间,并且有n-1根树枝连接,每个房间都可以相互到达,且俩个房间之间的路线都是唯一的.天哪,他 ...
- BZOJ 3631: [JLOI2014]松鼠的新家( 树链剖分 )
裸树链剖分... ------------------------------------------------------------------- #include<bits/stdc++ ...
- Bzoj 3631: [JLOI2014]松鼠的新家(树链剖分+线段树)
3631: [JLOI2014]松鼠的新家 Time Limit: 10 Sec Memory Limit: 128 MB Description 松鼠的新家是一棵树,前几天刚刚装修了新家,新家有n个 ...
- 洛谷P3258 [JLOI2014]松鼠的新家(树上差分+树剖)
题目描述 松鼠的新家是一棵树,前几天刚刚装修了新家,新家有n个房间,并且有n-1根树枝连接,每个房间都可以相互到达,且俩个房间之间的路线都是唯一的.天哪,他居然真的住在”树“上. 松鼠想邀请小熊维尼前 ...
随机推荐
- Android SDK上手指南:示例项目
Android SDK上手指南:示例项目 2013-12-26 15:40 核子可乐译 51CTO 字号:T | T Android SDK示例项目中的应用能够执行种种功能,例如各类用户界面元素.数据 ...
- AdaBoost笔记之代码
最近要做二分类问题,先Mark一下知识点和代码,参考:Opencv2.4.9源码分析——Boosting 以下内容全部转自此文 一 原理 二 opencv源码 1.先看构建Boosting的参数: ...
- 用 vue 脚手架 vue-cli 初始化(新建)项目
前提:已经安装 node.js 和 git 1.在需要创建的文件夹中,右击 -> Git Bush Here ,然后输入命令 vue init webpack "文件名"( ...
- idea报错:Error:java不支持发行版本5的解决方法
将以下对应配置一致即可. File-->Project Structure File-->Settings
- matlab实现一次性实现多个文件夹图片转化为.mat文件
%这里是主函数:命名为readImg.m; clc;clear; %---read_image; filepath = 'G:\人脸重建\data\src_all\';%图片路径可以根据自己需要修改; ...
- PHP 缓存详解
为什么要使用缓存 一个网站或者一个应用的标准流程是浏览器向应用服务器发出请求,应用服务器做一些计算和逻辑判断之后再请求数据库,数据库收到请求后在经过计算将数据返回给应用服务器,应用服务器再次计算后把数 ...
- shell中各种括号的作用详解()、(())、[]、[[]]、{}
一.小括号,圆括号() 1.单小括号 () ①命令组.括号中的命令将会新开一个子shell顺序执行,所以括号中的变量不能够被脚本余下的部分使用.括号中多个命令之间用分号隔开,最后一个命令可以没有分号, ...
- python3没有了xrange
升级到python3的同学应该会注意到以前经常用的xrange没了! 是的,python3的range就是xrange.直接看效果! Python 2.7.13 (v2.7.13:a06454b1 ...
- js校验文本框只能输入数字(包括小数)
form表单 <form method="POST" action=""> <input type="text" id=& ...
- agc014F Strange Sorting
这套题比较简单,以为自己能够独立A掉D和E,或许就能自己A掉F,看来还真是想多了 题意:给一个$n$的全排列,每次操作把$max(a[1],a[2],...,a[i]) = a[i]$的记为$high ...