票(NKOJ-9512)题解

问题描述

给定一棵树,每次从节点i走到节点i+1,经过树上的边需要收费,对于每条边,可以选择价格为c_i的单程票或价格为d_i的多程票,求最小代价。

解题思路

1.基本思路

不难想到统计每条边经过次数,对于每条边比较两种方案代价,求和即可。

于是可以先求出每次将链\left ( i,i+1 \right )上的边加权值加1后,每条边的权值。

2.暴力算法

每次深搜求链所有经过的边,将这些边权值加1。

时间复杂度O\left ( n^2 \right ) ,空间复杂度O(n)

3.树上差分

首先,对于每条边,将其权值记录在其连接的深度较大的节点中。

w=lca\left ( u,v\right ),对于将链\left ( u,v \right )所有边的权值加d,则需将链(u,root),(v,root)边上权值加d,将(w,root)边上权值减2d

用到差分思想,每个节点维护值b,使节点u权值等于子树b值之和。于是将(u,root)边上的值加d

只需将节点ub值加d

由于本题要求在所有修改后进行查询,所以可以直接维护b值并在所有修改后求出所有节点权值。

时间复杂度O(n),空间复杂度O(n)

4.DFS序实现

在DFS序中,节点的子树在一个区间内。在DFS过程中记录时间戳表示节点子树的区间。

用树状数组维护b值,查询时只需求DFS序上子树表示区间之和。

时间复杂度O(n\cdot log_{2}n),空间复杂度O(n)

示例代码

#include<bits/stdc++.h>

#define int long long

using namespace std;

const int N=3e5+5,M=N*2,K=40;

int n;
int a[N],b[N],c1[N],c2[N];
int la[N],ne[M],en[M],idx;
int dfi[N][2],dfi_;
int depth[N];
int fa[N][K];
int c[N];
int ans;

void add(int a,int b)
{
	idx++;
	ne[idx]=la[a];
	la[a]=idx;
	en[idx]=b;
}

void dfs(int u,int father)
{
	dfi[u][0]=++dfi_;
	
	fa[u][0]=father;
	for(int k=1;k<K;k++)
	{
		fa[u][k]=fa[fa[u][k-1]][k-1];
		if(!fa[u][k])break;
	}
	
	depth[u]=depth[father]+1;
	
	for(int j=la[u];j;j=ne[j])
	{
		int v=en[j];
		if(v!=father)dfs(v,u);
	}
	
	dfi[u][1]=dfi_;
}

int lca(int u,int v)
{
	if(depth[u]>depth[v])swap(u,v);
	
	for(int k=K-1;k>=0;k--)
		if(depth[fa[v][k]]>=depth[u])v=fa[v][k];
	
	if(u==v)return u;
	
	for(int k=K-1;k>=0;k--)
		if(fa[u][k]!=fa[v][k])
			u=fa[u][k],v=fa[v][k];
	
	return fa[u][0];
}

inline int lowbit(int x)
{
	return x&-x;
}

void change(int x,int d)
{
	for(;x<=n;x+=lowbit(x))
		c[x]+=d;
}

int getsum(int x)
{
	int s=0;
	for(;x;x-=lowbit(x))
		s+=c[x];
	return s;
}

void modify_one(int u,int d)
{
	change(dfi[u][0],d);
}

void modify(int u,int v)
{
	int w=lca(u,v);
	modify_one(u,1);
	modify_one(v,1);
	modify_one(w,-2);
}

int query(int u)
{
	return getsum(dfi[u][1])-getsum(dfi[u][0]-1);
}

signed main()
{
	scanf("%lld",&n);
	for(int i=1;i<n;i++)
	{
		scanf("%lld%lld%lld%lld",&a[i],&b[i],&c1[i],&c2[i]);
		add(a[i],b[i]);
		add(b[i],a[i]);
	}
	
	dfs(1,0);
	
	for(int i=1;i<n;i++)modify(i,i+1);
	
	for(int i=1;i<n;i++)
	{
		int u=a[i],v=b[i];
		if(fa[u][0]==v)ans+=min(c2[i],c1[i]*query(u));
		else ans+=min(c2[i],c1[i]*query(v));
	}
	
	printf("%lld",ans);
	
	return 0;
}

补充

这里再说下链修改维护节点权值的方法。

w=lca(u,v),将链(u,v)上节点的权值加d,则需将链(u,root),(v,root)点上权值加d,将(w,root)点上权值减d,若w不是根节点,将链(father_w,root)点上权值减d

差分思想相同。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值