zoukankan      html  css  js  c++  java
  • 「CF1000G Two-Paths」

    题目大意

    给出一棵树,多次查询两个点之间的 (mathcal{Two-Path}) 的权值的最大值,以下为 (mathcal{Two-Path}) 路径权值的定义:

    [value=sumlimits_{pin path}a_p-sumlimits_{ein path}(w_ek_e) ]

    (其中 (a_i) 表示 (i) 的点权,(w_e) 表示边 (e) 的边权,(k_e) 表示边 (e) 被经过的次数,且要求 (0leq k_eleq 2),即每条边最多被走过两次).

    分析

    假如给出的树长这个样子:

    考虑有一次 (3 o 6) 的查询.

    显然对于 (3 o 6) 这条简单路径上的边是只能走一次的(如果走了至少 (2) 次,那么肯定要走至少 (3) 次,这样是不合法的),所以考虑贡献的时候只需要去考虑不在这条简单路径上的部分即可,也就是如下部分(蓝色和绿色部分):

    其中绿色部分是路径上的点的部分子节点的贡献,蓝色部分仅在 (operatorname{LCA}(3,6))(每次查询的两个节点的最近公共祖先)处存在.先考虑绿色部分的贡献.

    显然可以先处理出 (f_i) 表示 (i) 节点的所有子节点对 (i) 的贡献的最大值,显然有如下转移方程:

    [f_i=sumlimits_{uin son_i}max{f_u-2*VAL+val_u,0} ]

    ((VAL_u) 表示 (u)(u) 的父亲这条边的边权,(val_u) 表示 (u) 节点的权值)

    但是对于两个点查询的时候并不能把路径上所以的 (f) 加起来,如下图:

    我们需要的是蓝色部分以及绿色部分的贡献,但是对于 (4) 号节点存的贡献是红色部分给它的,但实际应该是只能有绿色部分,也就是说要减去蓝色部分的贡献,再理解理解后就可以得到以下式子:

    [p=f_i+f_{fa}-{color{red}max{f_i-2*VAL_i+val_{i},0}} ]

    ((p) 表示 (i,fa) 的部分对当前这次查询的贡献,(fa)(i) 的父节点,(fa,i) 都是查询两个点再树上的简单路径上的点((i)(u)(v),如果 (i ot= u)(i ot= v),那么对于这条路径上的 (i) 的子节点也需要减去自身对 (i) 的贡献))

    其中红色部分其实就是 (i)(fa) 的贡献,将这部分拎出来就变成了所以非 (operatorname{LCA}) 的节点都需要减去自己为根节点的子树对父节点的贡献.因为这个东西是固定的,所以可以直接树上差分快速计算.这样就可以通过计算一次 (operatorname{LCA}) 的复杂度计算出所有路径节点的子树对路径的贡献.

    下面只需要计算出 (operatorname{LCA}) 的父节点对它的贡献即可,可以发现这个东西可以换根 (operatorname{dp}) 求出:

    [g_i=max{g_{fa}+f_{fa}+val_{fa}-2*VAL_{i}-max{f_i-2*VAL_i+val_{i},0},0} ]

    ((g_i) 表示 (i) 的父节点对 (i) 的贡献,具体推导比较显然,不展开)

    对于查询两点的简单路径的贡献也可以直接树上差分,所以单次查询的复杂度 (=) 计算两点 (operatorname{LCA}) 的复杂度.

    因为不会 (mathcal{Tarjan LCA}) 所以用了树剖,但还是比倍增做法快了不少.

    代码

    #include<bits/stdc++.h>
    #define REP(i,first,last) for(int i=first;i<=last;++i)
    #define DOW(i,first,last) for(int i=first;last<=i;--i)
    namespace IO
    //快读模板
    using namespace IO;
    using namespace std;
    const int MAXN=3e5+5;
    const int MAXM=MAXN<<1;
    int n,m;
    long long val[MAXN];
    struct Edge
    {
    	int to,next;
    	long long val;
    }edge[MAXM];
    int edge_head[MAXN];
    int edge_cnt=0;
    #define FOR(now) for(int edge_i=edge_head[now];edge_i;edge_i=edge[edge_i].next)
    #define TO edge[edge_i].to
    #define VAL edge[edge_i].val
    inline void AddEdge(int from,int to,long long val)
    {
    	edge[++edge_cnt].to=to;
    	edge[edge_cnt].val=val;
    	edge[edge_cnt].next=edge_head[from];
    	edge_head[from]=edge_cnt;
    }
    long long f[MAXN];//f 同分析中的 f
    long long sumf[MAXN];//f 函数的树上前缀和
    long long d[MAXN];//每个点对父节点的贡献
    long long sumdec[MAXN];//需要减去部分的树上前缀和
    long long tof[MAXN];//每个点到父节点的边的边权
    long long sump[MAXN],sume[MAXN];//点权与边权的树上前缀和
    long long g[MAXN];//g 同分析中的 g
    //以下变量为树剖所需变量
    int point_deep[MAXN];
    int point_father[MAXN];
    int point_son[MAXN];
    int point_size[MAXN];
    int chain_cnt=0;
    int point_id[MAXN];
    int point_val[MAXN];
    int chain_top[MAXN];
    //树剖部分不会做说明,如有不理解建议先做模板题
    void DFS_1(int now=1)//第一次 DFS
    {
    	sump[now]=sump[point_father[now]]+val[now];//树上前缀点权
    	int max_size=-1;
    	point_size[now]=1;
    	FOR(now)
    	{
    		if(point_father[now]!=TO)
    		{
    			point_father[TO]=now;
    			point_deep[TO]=point_deep[now]+1;
    			tof[TO]=VAL;
    			sume[TO]=sume[now]+VAL;//树上前缀边权
    			DFS_1(TO);
    			f[now]+=d[TO];//计算 f
    			point_size[now]+=point_size[TO];
    			if(point_size[TO]>max_size)
    			{
    				max_size=point_size[TO];
    				point_son[now]=TO;
    			}
    		}
    	}
    	d[now]=max(f[now]-2*tof[now]+val[now],0ll);//计算这个点对父节点的贡献
    }
    void DFS_2(int now=1,int top=1,long long on=0/*从父节点转移来的 gi*/)
    {
    	g[now]=on;
    	sumf[now]=sumf[point_father[now]]+f[now];
    	sumdec[now]=sumdec[point_father[now]]+d[now];
    	point_id[now]=++chain_cnt;
    	point_val[chain_cnt]=val[now];
    	chain_top[now]=top;
    	if(!point_son[now])
    	{
    		return;
    	}
    	DFS_2(point_son[now],top,max(on+f[now]+val[now]-d[point_son[now]]-2*tof[point_son[now]],0ll));
    	FOR(now)
    	{
    		if(TO!=point_father[now]&&TO!=point_son[now])
    		{
    			DFS_2(TO,TO,max(on+f[now]+val[now]-d[TO]-2*VAL,0ll));
    		}
    	}
    }
    int LCA(int u,int v)
    {
    	while(chain_top[u]!=chain_top[v])
    	{
    		if(point_deep[chain_top[u]]<point_deep[chain_top[v]])
    		{
    			swap(u,v);
    		}
    		u=point_father[chain_top[u]];
    	}
    	if(point_deep[v]<point_deep[u])
    	{
    		swap(u,v);
    	}
    	return u;
    }
    long long Query(int u,int v)
    {
    	int lca=LCA(u,v);
    	return sumf[u]+sumf[v]-2*sumf[lca]+f[lca]-sumdec[u]-sumdec[v]+2*sumdec[lca]+g[lca]-sume[u]-sume[v]+2*sume[lca]+sump[u]+sump[v]-sump[lca]-sump[point_father[lca]];//又臭又长的树上差分
    }
    
    int main()
    {
    	Read(n,m);
    	REP(i,1,n)
    	{
    		Read(val[i]);
    	}
    	int u,v,w;
    	REP(i,1,n-1)
    	{
    		Read(u,v,w);
    		AddEdge(u,v,w);
    		AddEdge(v,u,w);
    	}
    	DFS_1();
    	DFS_2();
    	REP(i,1,m)
    	{
    		Read(u,v);
    		Writeln(Query(u,v));
    	}
    	return 0;
    }
    
  • 相关阅读:
    linux上用selenium登录新浪微博,获取用户关注的用户id
    JS、Python对字符串的编码函数
    在ubuntu系统下装hadoop
    windows下python3.x的安装与使用
    python多线程、多进程、协程的使用
    python简单操作redis
    操作系统基础知识
    排序算法汇总
    网易的突然袭击
    小红书视频面试
  • 原文地址:https://www.cnblogs.com/Sxy_Limit/p/13828798.html
Copyright © 2011-2022 走看看