【树上维护】 购物

题目:(不想看题滑到下面去)
Eert市共有N个车站,这N个车站由N-1条双向公路相连,任意一个车站都可以到达其他所有车站。最初坐车通过第i条公路所需的时间为ti。Eert市的任何任何工作、生活设施均设置在车站处。也就是说,公路中间不会是任何人的居住或工作场所。并且,车站i的居住人口为pi。Eert市仅有一个市场,设置在1号车站处。Eert市有着特殊的地区划分规则:将市场看作根,则整座城市成为一棵有根树,这时称以i为根的子树为地区i(这里地区有层层嵌套的关系,是不是觉得很神奇?)。

Eert市民每天都必须在居住地和工作地之间坐车往返,并且在途中顺道去超市购物。也就是说,它们会在居住地到工作地路上到市场所需时间最少的一个车站处坐车去顺便购物,而我们把这个车站叫做中转车站。

对于一对车站(无序对,且这两个车站可以相同),我们定义偏远指数为:两车站人口之积乘以中转车站坐车到市场所需的时间。

对于地区i,它的偏远指数为:地区i中每对车站(与上面一致,是无序对,且两车站可以相同)的偏远指数之和。

Eert市的基本布局是不会改变的,人口分布和数量也不会改变。但是,政府会不时调整通过公路所需的时间即ti。

你需要为政府开发一个程序以随时查询某一地区的偏远指数。

输入
第一行两个整数N,M,分别为车站个数和事件个数。

接下来一行N个整数,为人口pi。

接下来N-1行,每行三个整数ai,bi,ti,为公路连接的两个车站和初始时通过公路所需的时间。

接下来M行,每行描述一个事件,为以下两种之一(每种第一个数字为输入的时间类型):

1 a 询问地区a的偏远指数

2 a c 政府将通过公路a的时间改成了c

输出
对于每一个询问操作,输出一行一个整数为答案。因为答案可能很大,只需要输出偏远指数对取10^9+7模后的结果即可

样例输入
5 4
1 2 3 4 5
1 2 10
1 3 10
1 4 10
2 5 10
1 1
1 2
2 1 20
1 1
样例输出
890
640
1280
数据: 对于100%的数据,1<=N,M<=100000, 1<=ti,pi,c<10^9+7。

题解:
题目大意:给定一棵树,有点权w和边权,支持修改边权,查询以某个点为根的子树中所有无序点对(x,y)的wx * wy * dist(1,lca(x,y))的和
无序点对可以为(x,x)

分析:
我们发现我们需要统计的是以某个点x为lca 的所有无序点对的点权积的和乘上dist(1,x),而以x 为lca 的点对一定在x的不同子树中,且在x的不同子树中的无序点对的lca一定是x,所以这个值可以转化为W1 * W2 + W2 * W3+ W1 * W3 + …
根据乘法分配律我们可以得到原式=W2 * W1 + W3 *(W2 + W1) + W4 * (W1 + W2 + W3) + …
所以维护前缀和即可统计该值,lca的求法就不再赘述
然后接下来考虑修改和查询
询问点x(x的dfn为a)的式子为
在这里插入图片描述

T为每次修改的边权的和(仅统计对这个点子树有影响的)
我们将式子拆开得到

在这里插入图片描述
而我们已经求出一个点i的

在这里插入图片描述
所以前缀和统计

在这里插入图片描述
然后将剩余的部分插入线段树进行修改
我们发现修改一条边仅会影响到这条边的深度较大的端点以及它的子树的dist
所以可以用树剖或者dfs序进行区间修改
然后查询自然就是区间求和

预处理树剖:dep是到1号点的距离
deep是深度
weihu是需要往线段树或树状数组里插入的值
sumsub是子树当前点权和(包括它本身)

void dfs(int v)
{
	ll maxn=0;
	ll modif=p[v].val;
	pt[v]=1;
	dfn[v]=++cnt;
	p[v].siz=1;
	for(int i=head[v];i;i=nxt[i])
	{
		int y=vis[i];
		if(pt[y]) continue;
		p[y].deep=p[v].deep+1;
		p[y].fa=v;
		p[y].dep=p[v].dep+c[i];
		dfs(y);
		p[v].sumsub+=p[y].sumsub;
		p[v].weihu=(p[v].weihu%mod+((modif%mod)*(p[y].sumsub%mod))%mod)%mod;
		modif=(modif%mod+p[y].sumsub%mod)%mod;
		p[v].siz+=p[y].siz;
		if(maxn<p[y].siz) p[v].hson=y,maxn=p[y].siz;
	}
}
void dfs2(int x)
{
	p[x].top=x==p[p[x].fa].hson?p[p[x].fa].top:x;
	for(int i=head[x];i;i=nxt[i])
	{
		if(vis[i]!=p[x].fa)
		dfs2(vis[i]);
	}
	return;
}

求LCA:

int lca(int x,int y)
{
	for(;p[x].top!=p[y].top;)
        p[p[x].top].deep > p[p[y].top].deep ? x = p[p[x].top].fa : y = p[p[y].top].fa;
    return p[x].deep < p[y].deep ? x:y;
}

init:

for(int i=1;i<=n;i++) {scanf("%d",&p[i].val);p[i].weihu=p[i].val*p[i].val;p[i].sumsub=p[i].val;}

求前缀和顺便求a(线段树初始数组)

	for(int i=1;i<=n;i++) ma.insert(make_pair(i,dfn[i]));
	pre[0]=0;int k=0;
	map<int,int>::iterator it;
	for(it=ma.begin();it!=ma.end();it++)
	{
		int doo=it->second;
		pre[++k]=(pre[k-1]%mod+(p[doo].weihu%mod*p[doo].dep%mod)%mod)%mod;
		a[k]=p[doo].weihu%mod;
	}

线段树(略)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值