P3258 [JLOI2014]松鼠的新家


P3258 [JLOI2014]松鼠的新家

题意:

n个点,n-1条边,给出每个点的拜访顺序,问每个点经过几次(最后一次移动不算拜访)

题解:

题意明确后就很好做了,对于给定的x和y,我们只需要分别将x和y到lca(x,y)经过的点加一即可
但是这样直接做肯定不行
有两个方法:

  1. 树上差分
  2. 树链剖分

树上差分

参考题解
我们先考虑对于数组,我们指定连续一段加一
我们现在对a2到a6区间进行加一
现在处理差分数组,我们对a2加一,对a7减一
差分属猪的定义:a[i] = a[i-1] + 差分数组[i]
也就是我们并没有改变区间的值,而是改变的两个数之间的相对大小
在这里插入图片描述
对于树上差分:
父亲节点u = 其所有的子节点 + 他本身的差分数组
我们现在改变S到T边上所有点的值
我们对S的父亲节点减1,对T加1

//把s->t路径上所有点均加w,
chafen[t] += w;
chafen[s的父节点] -= w; 

当计算每个点具体值时:


for(遍历与 u 相连的每一个子节点 v){
	num[u] += num[v]; 
}  
num[u] += chafen[u];//加上差分数组 

在这里插入图片描述
在本题中结合lca即可

代码:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;

const int maxn = 300050;
const int maxm = maxn << 1;
int N, M;
int a[maxn], t1, t2;
int head[maxn], cnt;

struct Edge{
	int u, v, next;
}edge[maxm];

inline void addedge(int u, int v){
	edge[++cnt].u = u;
	edge[cnt].v = v;
	edge[cnt].next = head[u];
	head[u] = cnt;
}

int fa[maxn][31], dep[maxn];

void dfs(int u, int faa){
	fa[u][0] = faa, dep[u] = dep[faa] + 1;
	for(int i = 1; i <= 30; i++){
		fa[u][i] = fa[ fa[u][i - 1] ][i - 1];
	}
	for(int i = head[u]; i ; i = edge[i].next){
		int v = edge[i].v;
		if(v == faa)continue;
		dfs(v, u);
	}
} 

inline int lca(int x, int y){
	if(dep[x] < dep[y])swap(x,y);
	for(int i = 30; i >= 0; i--){
		if(dep[ fa[x][i] ] >= dep[y]) x = fa[x][i];
	}
	if(x == y)return x;
	for(int i = 30; i >= 0; i--){
		if(fa[x][i] != fa[y][i]){
			x = fa[x][i], y = fa[y][i];
		}
	}
	return fa[x][0];
}

int num[maxn];

int answer(int u, int faa){
	for(int i = head[u]; i ; i = edge[i].next){
		int v = edge[i].v;
		if(v == faa)continue;
		answer(v, u);
		num[u] += num[v];
	}
}
int main(){
	cin>>N;
	for(int i = 1; i <= N; i++){
		cin>> a[i];
	}
	for(int i = 1; i < N; i++){
		cin>> t1>> t2;
		addedge(t1, t2);
		addedge(t2, t1);
	}
	dfs(1, 0);
	for(int i = 1; i <= N - 1; i++){
		int u = a[i], v = a[i + 1];
		int t = lca(u, v);
		num[ fa[t][0] ]	-= 1;
		num[ t ] -= 1;
		num[ u ] += 1;
		num[ v ] += 1;
	}
	answer(1,0);
	for(int i = 2; i <= N; i++){
		num[a[i]]--;
	}
	for(int i = 1; i <= N; i++){
		cout<<num[i]<<endl;
	}
}

树链剖分

树链剖分就直接进行区间修改加一就行哈

代码:

这个代码我调了半个晚上,哭了哭了,终于调好了
在这里插入图片描述

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
inline int read(){
   int s=0,w=1;
   char ch=getchar();
   while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
   while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();//s=(s<<3)+(s<<1)+(ch^48);
   return s*w;
}
const int maxn=1e6+9;
int a[maxn];
struct node{
	int u,v,next;
}edge[maxn];
int cnt=0;
int tot=0;
int n;
int head[maxn];
void add(int u,int v)
{
	edge[++cnt].v=v;
	edge[cnt].next=head[u];
	head[u]=cnt;
}
//---
int tr[maxn<<2],laz[maxn<<2];
inline void pushup(int rt)
{
	tr[rt]=tr[rt<<1]+tr[rt<<1|1];
}
inline void pushdown(int rt,int l,int r)
{
	int len=(r-l+1);
	laz[rt<<1]+=laz[rt];
	laz[rt<<1|1]+=laz[rt];
	
	tr[rt<<1]+=laz[rt]*(len-(len>>1));
	tr[rt<<1|1]+=laz[rt]*(len>>1);
	
	laz[rt]=0;
}
inline void build(int rt,int l,int r)
{
	if(l==r)
	{
		tr[rt]=0;
		return ;
	}
	int mid=l+r>>1; 
	build(rt<<1,l,mid);
	build(rt<<1|1,mid+1,r);
	pushup(rt); 
}
inline int query(int rt,int l,int r,int ip)
{
	if(l==ip&&r==ip)return tr[rt];
	if(laz[rt])pushdown(rt,l,r);
	int mid=l+r>>1;
	int ress=0;
	if(ip<=mid)ress+=query(rt<<1,l,mid,ip);
	if(ip>mid)ress+=query(rt<<1|1,mid+1,r,ip);
	return ress;
}
inline void update(int rt,int l,int r,int L,int R,int k)
{
	if(L<=l&&r<=R)
	{
		int len=(r-l+1);
		laz[rt]+=k;
		tr[rt]+=k*len;
	}
	else 
	{
		if(laz[rt])pushdown(rt,l,r);
		int mid=l+r>>1;
		if(L<=mid)update(rt<<1,l,mid,L,R,k);
		if(R>mid)update(rt<<1|1,mid+1,r,L,R,k);
		pushup(rt);
	}
}
//---
int fa[maxn],dep[maxn],siz[maxn],id[maxn],top[maxn],son[maxn];
inline int updrange(int x,int y,int k)
{
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		update(1,1,n,id[top[x]],id[x],k);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y])swap(x,y);
	update(1,1,n,id[x],id[y],k);
}
inline void dfs1(int x,int f,int deep)
{
	dep[x]=deep;
	fa[x]=f;
	siz[x]=1;
	int maxson=-1;
	for(int i=head[x];i;i=edge[i].next)
	{
		int v=edge[i].v;
		if(v==f)continue;
		dfs1(v,x,deep+1);
		siz[x]+=siz[v];
		if(siz[v]>maxson)
		{
			son[x]=v;
			maxson=siz[v];
		}
	}
}
inline void dfs2(int x,int topf)
{
	id[x]=++tot;
	top[x]=topf;
	if(!son[x])return ;
	dfs2(son[x],topf);
	for(int i=head[x];i;i=edge[i].next)
	{
		int v=edge[i].v;
		if(v==fa[x]||v==son[x])continue;
		dfs2(v,v);
	}
}
void print()
{
		for(int i=1;i<=n;i++)
	{
		printf("%d\n",query(1,1,n,id[i]));
		//cout<<<<endl;
	}
//	printf("----\n");
}
int main()
{
	
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1;i<n;i++)
	{
		int x,y;
		cin>>x>>y;
		add(x,y);
		add(y,x);
	}
	dfs1(a[n],0,1);
	dfs2(a[n],a[n]);
	build(1,1,n);
	updrange(a[1],a[1],1);
	//print();
	for(int i=1;i<n;i++)
	{
		updrange(a[i],a[i+1],1);
		updrange(a[i],a[i],-1);
	//	print();
	}
	updrange(a[n],a[n],-1);
	print();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值