CF1000G
蜜汁树形dp...
首先分析一下:他要求一条边至多只能经过两次,那么很容易会发现:从x到y这一条路径上的所有边都只会被经过一次。(如果过去再回来那么还要过去,这样就三次了,显然不合法)
那么其他能产生贡献的部分就只有一下几个部分:x,y的子树内部,LCA(x,y)的上半部分的树以及x-y路径上的点向外延伸所形成的部分
这三部分互相独立又互相关联,所以我们设计三个dp对他们进行转移
记dp1[x]代表x的子树内所形成的的贡献,dp3[x]表示x以上的树所形成的贡献(包括x的兄弟节点)
这样就设计出了第一个和第三个状态
至于第二个,我们可以发现这个情况等价于路径上所有点向他的所有兄弟节点去跑,这样延伸出来的一种情况。
那么我们设计dp2[x]代表x的兄弟节点对x的贡献
接下来我们考虑转移:
首先,dp1非常好转移,只需向下dfs,每次回溯时只要能产生正的贡献就向上更新,同时记录每个点是否可以向上更新即可
当dp1出来了之后,dp2也就很好转移了,因为如果父节点的dp1没有利用这个节点进行更新,那么这个节点的dp2就是他父节点的dp1
如果dp1利用了这个节点进行更新,那就将dp1减掉这个节点提供的贡献赋给dp2即可
而dp3,很显然dp3要分为两部分,一部分是父节点向上,一部分是兄弟节点,兄弟节点部分就是dp2,而父节点向上那就是父节点的dp3,这也就完成了更新
这样三个dp就维护出来了
如果对概念不是特别清楚,画几个图来理解一下:
那么更新完这三个,查询也就变得简单了:首先统计x-y路径上的部分,然后统计x子树内,y子树内,LCA(x,y)以上的部分,以及x-y路径上的点向外延伸的部分,而这部分可以在树链上用前缀和维护。
但是这里有个小问题:由于x和y在跳到LCA上时会跳到LCA的两个子节点上,那么对这两个子节点,我们不能加两次兄弟节点的贡献(这样就加重了),所以我们去掉一部分即可。
贴代码:
#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#define ll long long
using namespace std;
struct Edge
{
int next;
int to;
ll val;
}edge[600005];
int head[300005];
int f[300005][30];
ll dp1[300005];
ll dp2[300005];
ll dp3[300005];
ll fv[300005];
bool used1[300005];
ll dis[300005];//边权距离
ll d[300005];//点权距离
ll v[300005];
ll s[300005];
int dep[300005];
int cnt=1;
int n,q;
void init()
{
memset(head,-1,sizeof(head));
cnt=1;
}
void add(int l,int r,ll w)
{
edge[cnt].next=head[l];
edge[cnt].to=r;
edge[cnt].val=w;
head[l]=cnt++;
}
void dfs(int x,int fx)//处理dp1
{
f[x][0]=fx;
dep[x]=dep[fx]+1;
for(int i=head[x];i!=-1;i=edge[i].next)
{
int to=edge[i].to;
if(to==fx)
{
continue;
}
fv[to]=edge[i].val;
dis[to]=dis[x]+edge[i].val;
d[to]=d[x]+v[to];
dfs(to,x);
if(dp1[to]+v[to]-2*edge[i].val>=0)
{
dp1[x]+=dp1[to]+v[to]-2*edge[i].val;
used1[to]=1;
}
}
for(int i=head[x];i!=-1;i=edge[i].next)
{
int to=edge[i].to;
if(to==fx)
{
continue;
}
if(!used1[to])
{
dp2[to]=dp1[x];
}else
{
dp2[to]=dp1[x]-(dp1[to]+v[to]-2*edge[i].val);
}
}
}
void redfs(int x,int fx)
{
s[x]+=dp2[x];
for(int i=head[x];i!=-1;i=edge[i].next)
{
int to=edge[i].to;
if(to==fx)
{
continue;
}
dp3[to]=max((ll)0,dp3[x]+v[x]-2*edge[i].val+dp2[to]);
s[to]+=s[x];
redfs(to,x);
}
}
void getf()
{
for(int i=1;i<=25;i++)
{
for(int j=1;j<=n;j++)
{
f[j][i]=f[f[j][i-1]][i-1];
}
}
}
int LCA(int x,int y)
{
if(dep[x]>dep[y])
{
swap(x,y);
}
for(int i=25;i>=0;i--)
{
if(dep[f[y][i]]>=dep[x])
{
y=f[y][i];
}
}
if(x==y)
{
return x;
}
int ret;
for(int i=25;i>=0;i--)
{
if(f[x][i]!=f[y][i])
{
x=f[x][i];
y=f[y][i];
}else
{
ret=f[x][i];
}
}
return ret;
}
ll cal(int x,int y)
{
ll ret=0;
if(dep[x]>dep[y])
{
swap(x,y);
}
int f1=LCA(x,y);
if(f1!=1)
{
ret+=d[x]+d[y]-d[f1]-d[f[f1][0]];
ret-=dis[x]+dis[y]-2*dis[f1];
}else
{
ret+=d[x]+d[y]-d[f1];
ret-=dis[x]+dis[y]-dis[f1];
}
if(x==f1)
{
ret+=dp1[y];
ret+=dp3[x];
ret+=s[y];
ret-=s[x];
}else
{
ret+=dp1[x];
ret+=dp1[y];
ret+=dp3[f1];
int ff1=x,ff2=y;
for(int i=25;i>=0;i--)
{
if(dep[f[ff1][i]]>dep[f1])
{
ff1=f[ff1][i];
}
if(dep[f[ff2][i]]>dep[f1])
{
ff2=f[ff2][i];
}
}
ret+=s[x]-s[ff1];
ret+=s[y]-s[ff2];
ret+=dp2[ff1];
if(used1[ff2])
{
ret-=dp1[ff2]+v[ff2]-2*fv[ff2];
}
}
return ret;
}
inline int read()
{
int f=1,x=0;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int main()
{
n=read(),q=read();
init();
for(int i=1;i<=n;i++)
{
v[i]=(ll)read();
}
for(int i=1;i<n;i++)
{
int x=read(),y=read(),z=read();
add(x,y,(ll)z);
add(y,x,(ll)z);
}
d[1]=v[1];
dfs(1,1);
getf();
redfs(1,1);
for(int i=1;i<=q;i++)
{
int x=read(),y=read();
printf("%lld\n",cal(x,y));
}
return 0;
}
CF1000G的更多相关文章
- CF1000G Two-Paths
题目大意:给你一棵树,其中点上和边上都有值.定义2-Path为经过一条边最多两次的路径,价值为经过点的权值加和-经过边权值*该边经过次数.4e5组询问,每次询问树上连接x,y两点的2-Path的最大价 ...
- CF1000G Two-Paths (树形DP)
题目大意:给你一棵树,点有点权$a_{i}$,边有边权$w_{e}$,定义一种路径称为$2-path$,每条边最多经过2次且该路径的权值为$\sum _{x} a_{x}\;-\;\sum_{e}w_ ...
随机推荐
- 基于【字节】操作的IO接口:InputStream、OutputStream
InputStream 参考链接:对java中FileInputStream.BufferInputStream的理解 /** * Author:Mr.X * Date:2017/10/9 17:11 ...
- 推荐前端框架 & 模板
BootStrap Semantic UI Pure Amazeui(前后端都有,很丰富) amazeui http://tpl.amazeui.org AdminLTE AdminLTE https ...
- printf是在libc库中么?
libc中果然有很多的函数,使用nm看了一下,里面竟然还有reboot函数,汗! 使用grep,可以看到各种 printf 也都在这里头. objdump是看函数的地址与函数名对应的,虽然也能证明pr ...
- pycharm 使用教程
本文为大家讲解的是pycharm 的安装和首次使用,PyCharm 是我用过的python编辑器中,比较顺手的一个.而且可以跨平台,在macos和windows下面都可以用,这点比较好.感兴趣的同学参 ...
- 列式数据库~clickhouse问题汇总
一 简介:常见的clickhouse 问题汇总 二 问题系列 1 内存问题 Code: 241. DB::Exception: Received from localhost:9000, : ...
- java json 转换
1.直接输出: 2.字符串 通过eval转换输出,里面涉及到一个转义问题,还要注意eval的用法里面需要加"("+ + ")" 3.
- openstack Q版部署-----glance安装配置(4)
镜像服务(glance)使用户能够发现,注册和检索虚拟机镜像. 它提供了一个REST API,使您可以查询虚拟机镜像元数据并检索实际镜像. 您可以将通过镜像服务提供的虚拟机映像存储在各种位置,从简单的 ...
- 第四节:tensorflow图的基本操作
基本使用 使用图(graph)来表示计算任务 激活会话(Session)执行图 使用张量(tensor)表示数据 定义变量(Variable) 使用feed可以任意赋值或者从中获取数据,通常与占位符一 ...
- ROS tf 两个常用的函数
/** \brief Get the transform between two frames by frame ID. * \param target_frame The frame to wh ...
- struts2框架之拦截器(参考第二天学习笔记)
拦截器 1. 什么是拦截器 1). 与JavaWeb中的Filter比较相似. 2). 拦截器只能拦截Action!!! 2. Struts中定义了很多拦截器,其中defaultStack中的拦截器会 ...