洛谷 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根树枝连接,每个房间都可以相互到达,且俩个房间之间的路线都是唯一的.天哪,他居然真的住在”树“上. 松鼠想邀请小熊维尼前 ...
随机推荐
- linux学习(二)-----Linux 的目录结构、远程登录、vi和vim
linux目录结构 基本介绍 linux 的文件系统是采用级层式的树状目录结构,在此结构中的最上层是根目录“/”,然后在此 目录下再创建其他的目录. 目录结构具体介绍 Linux 目录总结 1.lin ...
- AdaBoost笔记之代码
最近要做二分类问题,先Mark一下知识点和代码,参考:Opencv2.4.9源码分析——Boosting 以下内容全部转自此文 一 原理 二 opencv源码 1.先看构建Boosting的参数: ...
- c++ stream操作杂记
包含简单的读写文件,供初学者入门,stream目前包含三种,终端,文件,内存,注意宽字节. ) ; ) ; ] ; } pasting
- jeecmsv8.1怎么修改项目后台访问地址
将jeeadmin/jeecms/index.do 改为admin/index.do为例 1.修改WebContent\WEB-INF\web.xml <servlet-mapping> ...
- PAT甲级——A1055 The World's Richest
Forbes magazine publishes every year its list of billionaires based on the annual ranking of the wor ...
- JavaWeb-类加载器-注解-动态代理
(一)类加载器 1.什么是类加载器,作用是什么? 类加载器就加载字节码文件(.class) 2.类加载器的种类 类加载器有三种,不同类加载器加载不同的 1)BootStrap:引导类加载器:加载都是最 ...
- Lamdba表达式的代码使用讲解
public class Lambda{ public static void main(String[] args) { repeat(10, (i)->System.out.print(&q ...
- 使用 windows 批处理指令(BAT文件)进行压缩文件(zip)解压操作
以下指令包括文件删除.复制.zip文件解压操作.使用7z指令指令进行解压操作前,需要确保 windows 的 path 系统环境变量中存在7z的安装路径. 7z的下载地址:https://www.7- ...
- mybatis学习:mybatis的二级缓存
一级缓存: 一级缓存是SqlSession级别的缓存.在操作数据库时需要构造 sqlSession对象,在对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据.不同的sqlSession ...
- Sql 竖表转横表
) set @sql='select t3.BID,t5.UnitName,Sort,UnitTypeSort' select @sql=@sql+' , max(case t4.id when '' ...