题目链接:

[NOI2018]情报中心

题目大意:给出一棵n个节点的树,边有非负边权,并给出m条链,对于每条链有一个代价,要求选出两条有公共边的链使两条链的并的边权和-两条链的代价和最大。

花了一天的时间,终于搞定了这道题,不可否认这真的是一道神题,对思维和代码能力的考察都非常到位。

通过手画或者数据范围的特殊性质都不难发现两条有公共边的链的$LCA$要么相同要么不同(这不废话吗?)

而两条链的并的计算方法也可以按照上面两种情况来分类计算,我们先分别讨论两种情况的计算方法再考虑如何一起考虑两种情况。

一、两条链LCA不同

对于两条链$LCA$不同的情况可以归为以下形式:

我们不妨设黄色链的长度为$len1$,代价为$val1$,$LCA$为$lca1$;蓝色链的长度为$len2$,代价为$val2$,$LCA$为$lca2$;红点深度为$dep1$。

那么答案可以记为:$ans=len1+len2-val1-val2-dep1+max(dep(lca1),dep(lca2))$

可以发现两条链的交必定是直上直下的并且两条链位于红点子树内的端点必定处于红点不同的子树内。

那么对于每个红点i,我们维护:

$f[i][j]$表示链一端点在$i$子树中,且链的$LCA$深度为$j$的链的长度-代价的最大值

$g[i][j]$表示链一端点在$i$子树中,且链的$LCA$深度为$j$的链的长度-代价+$LCA$的深度的最大值

这两个数组可以用线段树来维护,对于每一个点动态开点建一棵线段树,剩下的只需要从下往上在树上进行线段树合并即可。

在合并时,例如$x$为$y$的父节点,我们将$y$的线段树合并到$x$的线段树上。

因为$x$的线段树上当前保存的是$y$之前的$x$的子树中的信息,所以合并保证两条链的端点位于$x$不同的子树当中。

在合并时每当遍历到一个点我们用$x$线段树中这个点的左子树中$f$的最大值与$y$线段树中对应点右子树中$g$的最大值或$x$线段树中这个点右子树中$g$的最大值与$y$线段树中对应点左子树中$f$的最大值更新答案。

因为右子树代表的深度要比左子树代表的深度大,所以对于左子树只会用$f$来更新答案,右子树只会用$g$来更新答案。

那么如何保证合并的两条链有公共边呢?

我们对于树上的一个点$x$,假设它的深度为$dep$,那么在它合并完所有子树的线段树之后,将它的线段树中$[dep-1,n]$这一部分删除掉,因为回溯到的点的深度是$dep-1$,所以要保证线段树中留下来可合并的信息深度小于$dep-1$。

时间复杂度为$O(mlogn)$

二、两条链LCA相同

如图所示为两条链LCA相同的情况:

可以发现这种情况下的答案就是(两条链长之和+蓝点间距离+绿点间距离)/2-两条链代价之和。

我们同样设红色链长为$len1$,代价为$val1$,红链的蓝点深度为$dep1$;蓝色链长为$len2$,代价为$val2$,蓝链的蓝点深度为$dep2$,红点深度为$dep$,两绿点间距离为$dis$

那么$ans=\frac{len1-2*val1+dep1+len2-2*val2+dep2+dis-dep}{2}$

我们将红链的绿点权值设为$len1-2*val1+dep1$,蓝链的绿点权值设为$len2-2*val2+dep2$

再将每个绿点映射给对应的蓝点,定义两个绿点间的距离为两点间路径边权和+两点点权

这样我们只需要对蓝点做树形DP即可,对于每个蓝点维护这个点子树中所有点对应的绿点形成的树的直径的两端点。

将每个子节点的信息合并到父节点上并更新答案,注意更新答案的直径两端点要在合并的两个点信息中分别选一个点。

这样才能保证需要减掉的$dep$为当前合并的这个父节点的深度。

而这里的直径即为上述定义的两点间距离的最大值,这种直径同样符合可合并的那个结论,即:

将两棵树合并为一棵树时,新树的直径两端点为原来两棵树的直径的那四个端点中的两个。(证明参见树的直径及其性质与证明)

因为这种情况要求两条链的$LCA$相同,所以我们记录一下以每个点为$LCA$的链有哪些,将这些链的端点建虚树做上述的树形DP即可。

因为要保证两条链有公共边,所以如果父节点是当前枚举的这个$LCA$时就不合并或更新答案。

时间复杂度为$O(mlogn)$

三、排除两种情况之间的影响

这个问题想了好久,后来才发现其实保证这一点很简单。

对于两条链$LCA$相同的情况,因为我们是枚举每个点为$LCA$的情况来做的,所以保证了当前所有链的$LCA$都相同。

对于两条链$LCA$不同的情况,因为我们线段树合并时只用一个点的左子节点和右子节点来更新答案,所以保证了用于更新答案的两个链的$LCA$深度不同。

值得注意的是,因为我们无法知道两种情况中每个端点是属于上述的蓝点还是红点亦或绿点,所以对于一条链的两个端点都要存储信息,并且这样并不会影响答案的正确性。

#include<set>
#include<map>
#include<queue>
#include<stack>
#include<cmath>
#include<cstdio>
#include<vector>
#include<bitset>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
#define INF (1ll<<60)
#define pr pair<int,ll>
#define par pair<int,pr>
using namespace std;
char *p1,*p2,buf[100000];
#define nc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++)
int rd() {int x=0; char c=nc(); while(c<48) c=nc(); while(c>47) x=(((x<<2)+x)<<1)+(c^48),c=nc(); return x;}
ll rd2() {ll x=0; char c=nc(); while(c<48) c=nc(); while(c>47) x=(((x<<2)+x)<<1)+(c^48),c=nc(); return x;}
ll z;
int T;
ll ans;
int n,m;
int x,y;
int tot;
int dfn;
int cnt;
int num;
int top;
ll d[100010];
int s[100010];
int t[100010];
int st[100010];
int lg[200010];
int to[200010];
ll val[200010];
int vis[100010];
int dep[100010];
int ls[4000010];
int rs[4000010];
ll mx1[4000010];
ll mx2[4000010];
int next[200010];
int head[100010];
int root[100010];
int f[200010][19];
vector<int>q[100010];
vector<par>v[100010];
struct miku
{
int u[2];
ll v[2];
ll len;
miku()
{
u[0]=u[1]=0;
v[0]=v[1]=-INF;
len=-INF;
}
miku(int rt,ll val)
{
u[0]=rt;
v[0]=val;
u[1]=0;
v[1]=-INF;
len=-INF;
}
}tr[100010];
bool cmp(par x,par y)
{
return s[x.first]<s[y.first];
}
void add(int x,int y,ll z)
{
next[++tot]=head[x];
head[x]=tot;
to[tot]=y;
val[tot]=z;
}
void dfs(int x)
{
f[++dfn][0]=x;
s[x]=dfn;
for(int i=head[x];i;i=next[i])
{
dep[to[i]]=dep[x]+1;
d[to[i]]=d[x]+val[i];
dfs(to[i]);
f[++dfn][0]=x;
}
}
int mn(int x,int y)
{
return dep[x]<dep[y]?x:y;
}
void ST()
{
for(int i=2;i<=dfn;i++)
{
lg[i]=lg[i>>1]+1;
}
for(int j=1;j<=18;j++)
{
for(int i=1;i+(1<<j)-1<=dfn;i++)
{
f[i][j]=mn(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
}
}
int lca(int x,int y)
{
x=s[x];
y=s[y];
if(x>y)
{
swap(x,y);
}
int len=lg[y-x+1];
return mn(f[x][len],f[y-(1<<len)+1][len]);
}
ll dis(int x,int y)
{
return d[x]+d[y]-(d[lca(x,y)]<<1);
}
int build()
{
int rt=++cnt;
ls[rt]=rs[rt]=0;
mx1[rt]=mx2[rt]=-INF;
return rt;
}
void change(int &rt,int l,int r,int k,ll v1,ll v2,ll deep)
{
if(!rt)
{
rt=build();
}
mx1[rt]=max(mx1[rt],v1);
mx2[rt]=max(mx2[rt],v2);
if(l==r)
{
return ;
}
int mid=(l+r)>>1;
if(k<=mid)
{
ans=max(ans,v1+mx2[rs[rt]]-deep);
change(ls[rt],l,mid,k,v1,v2,deep);
}
else
{
ans=max(ans,v2+mx1[ls[rt]]-deep);
change(rs[rt],mid+1,r,k,v1,v2,deep);
}
}
void pushup(int rt)
{
mx1[rt]=max(mx1[ls[rt]],mx1[rs[rt]]);
mx2[rt]=max(mx2[ls[rt]],mx2[rs[rt]]);
}
void cut(int &rt,int l,int r,int k)
{
if(!rt)
{
return ;
}
if(l==r)
{
rt=0;
return ;
}
int mid=(l+r)>>1;
if(k<=mid)
{
rs[rt]=0;
cut(ls[rt],l,mid,k);
}
else
{
cut(rs[rt],mid+1,r,k);
}
pushup(rt);
}
int merge(int x,int y,int l,int r,ll deep)
{
if(!x||!y)
{
return x+y;
}
int mid=(l+r)>>1;
if(l==r)
{
mx1[x]=max(mx1[x],mx1[y]);
mx2[x]=max(mx2[x],mx2[y]);
return x;
}
ans=max(ans,mx1[ls[x]]+mx2[rs[y]]-deep);
ans=max(ans,mx1[ls[y]]+mx2[rs[x]]-deep);
ls[x]=merge(ls[x],ls[y],l,mid,deep);
rs[x]=merge(rs[x],rs[y],mid+1,r,deep);
pushup(x);
return x;
}
void dsu(int x)
{
for(int i=head[x];i;i=next[i])
{
dsu(to[i]);
root[x]=merge(root[x],root[to[i]],1,n,d[x]);
}
cut(root[x],1,n,dep[x]-1);
}
miku merge(miku &x,miku &y,ll deep)
{
miku res;
ll value;
res=x.len>y.len?x:y;
for(int i=0;i<2;i++)
{
for(int j=0;j<2;j++)
{
if(x.u[i]&&y.u[j])
{
value=dis(x.u[i],y.u[j])+x.v[i]+y.v[j];
ans=max(ans,(value-deep)>>1);
if(value>res.len)
{
res.u[0]=x.u[i];
res.u[1]=y.u[j];
res.v[0]=x.v[i];
res.v[1]=y.v[j];
res.len=value;
}
}
}
}
return res;
}
void dsu(int x,int rt)
{
int size=q[x].size();
for(int i=0;i<size;i++)
{
int to=q[x][i];
dsu(to,rt);
if(x!=rt)
{
tr[x]=merge(tr[x],tr[to],d[x]*2);
}
}
vis[x]=0;
q[x].clear();
}
void insert(int x)
{
int fa=lca(x,st[top]);
if(!vis[fa])
{
vis[fa]=1;
t[++num]=fa;
}
while(top>1&&dep[st[top-1]]>=dep[fa])
{
q[st[top-1]].push_back(st[top]);
top--;
}
if(st[top]!=fa)
{
q[fa].push_back(st[top]);
st[top]=fa;
}
st[++top]=x;
}
void work()
{
n=rd();
for(int i=1;i<=n;i++)
{
head[i]=0;
root[i]=0;
v[i].clear();
}
ans=-INF;
tot=0;
dfn=0;
cnt=0;
dep[1]=1;
for(int i=1;i<n;i++)
{
x=rd();y=rd();z=rd2();
add(x,y,z);
}
dfs(1);
ST();
m=rd();
for(int i=1;i<=m;i++)
{
x=rd();y=rd();z=rd2();
int fa=lca(x,y);
ll value=dis(x,y)-z;
if(x!=fa)
{
change(root[x],1,n,dep[fa],value,value+d[fa],d[x]);
v[fa].push_back(make_pair(x,make_pair(y,value+d[x]-z)));
}
if(y!=fa)
{
change(root[y],1,n,dep[fa],value,value+d[fa],d[y]);
v[fa].push_back(make_pair(y,make_pair(x,value+d[y]-z)));
}
}
dsu(1);
for(int i=1;i<=n;i++)
{
sort(v[i].begin(),v[i].end(),cmp);
top=0;
num=0;
st[++top]=i;
vis[i]=1;
t[++num]=i;
int size=v[i].size();
for(int j=0;j<size;j++)
{
if(!vis[v[i][j].first])
{
vis[v[i][j].first]=1;
t[++num]=v[i][j].first;
insert(v[i][j].first);
}
}
while(top>1)
{
q[st[top-1]].push_back(st[top]);
top--;
}
for(int j=1;j<=num;j++)
{
int now=t[j];
tr[now].u[0]=tr[now].u[1]=0;
tr[now].v[0]=tr[now].v[1]=-INF;
tr[now].len=-INF;
}
for(int j=0;j<size;j++)
{
miku now=miku(v[i][j].second.first,v[i][j].second.second);
tr[v[i][j].first]=merge(tr[v[i][j].first],now,d[v[i][j].first]*2);
}
dsu(i,i);
}
if(ans<=-1e16)
{
printf("F\n");
}
else
{
printf("%lld\n",ans);
}
}
int main()
{
T=rd();
mx1[0]=mx2[0]=-INF;
while(T--)
{
work();
}
}

BZOJ5419[Noi2018]情报中心——线段树合并+虚树+树形DP的更多相关文章

  1. luogu P4775 [NOI2018]情报中心 线段树合并 虚树 树的直径trick

    LINK:情报中心 神题! 写了一下午 写到肚子疼. 调了一晚上 调到ex 用的是网上dalao的方法 跑的挺快的. 对于链的暴力 我不太会kk. 直接说正解吧: 分类讨论两种情况: 1 答案的两条链 ...

  2. UOJ#397. 【NOI2018】情报中心 线段树合并 虚树

    原文链接www.cnblogs.com/zhouzhendong/p/UOJ397.com 前言 这真可做吗?只能贺题解啊-- 题解 我们称一条路径的 LCA 为这条路径两端点的 LCA. 我们将相交 ...

  3. Codeforces 1276F - Asterisk Substrings(SAM+线段树合并+虚树)

    Codeforces 题面传送门 & 洛谷题面传送门 SAM hot tea %%%%%%% 首先我们显然可以将所有能够得到的字符串分成六类:\(\varnothing,\text{*},s, ...

  4. 【PKUSC2019】树染色【线段树合并】【树形DP】

    Description 给出一棵n个点的树,现在有m种颜色,要给每个节点染色,相邻节点不能同色. 另外有k条限制,形如x号点不能为颜色y 同一节点有可能有多条限制. 求方案数对998244353取模的 ...

  5. 2016湖南省赛 I Tree Intersection(线段树合并,树链剖分)

    2016湖南省赛 I Tree Intersection(线段树合并,树链剖分) 传送门:https://ac.nowcoder.com/acm/contest/1112/I 题意: 给你一个n个结点 ...

  6. 【BZOJ5329】【SDOI2018】战略游戏(圆方树,虚树)

    [BZOJ5329][SDOI2018]战略游戏(圆方树,虚树) 题面 BZOJ 洛谷 Description 省选临近,放飞自我的小Q无心刷题,于是怂恿小C和他一起颓废,玩起了一款战略游戏. 这款战 ...

  7. bzoj5315/luoguP4517 [SDOI2018]战略游戏(圆方树,虚树)

    bzoj5315/luoguP4517 [SDOI2018]战略游戏(圆方树,虚树) bzoj Luogu 题目描述略(太长了) 题解时间 切掉一个点,连通性变化. 上圆方树. $ \sum |S| ...

  8. CF666E Forensic Examination SAM+线段树合并+前缀树倍增

    $ \color{#0066ff}{ 题目描述 }$ 给你一个串\(S\)以及一个字符串数组\(T[1..m]\),\(q\)次询问,每次问\(S\)的子串\(S[p_l..p_r]\)在\(T[l. ...

  9. [POI2011]ROT-Tree Rotations 线段树合并|主席树 / 逆序对

    题目[POI2011]ROT-Tree Rotations [Description] 现在有一棵二叉树,所有非叶子节点都有两个孩子.在每个叶子节点上有一个权值(有\(n\)个叶子节点,满足这些权值为 ...

随机推荐

  1. Java函数式编程和lambda表达式

    为什么要使用函数式编程 函数式编程更多时候是一种编程的思维方式,是种方法论.函数式与命令式编程的区别主要在于:函数式编程是告诉代码你要做什么,而命令式编程则是告诉代码要怎么做.说白了,函数式编程是基于 ...

  2. 升级MySQL5.7,开发不得不注意的坑

    前段时间,将线上MySQL数据库升级到了5.7.考虑到可能产生的不兼容性,在升级之前,确实也是战战兢兢,虽然测试环境,开发环境早在半年前就已提前升级. 基于前期的调研和朋友的反馈,与开发相关的主要有两 ...

  3. vue动态class——实现tag的选中状态

    vue动态class——实现tag的选中状态 <template> <div class="common-nav"> <div class=" ...

  4. centos7 network eno16777736

    Network service not running - eno16777736 not activated - CentOShttps://www.centos.org/forums/viewto ...

  5. JS—ajax及async和defer的区别

    ###1.ajax  “Asynchronous Javascript And XML”(异步 JavaScript 和 XML) 使用: 如不考虑旧版本浏览器兼容性, // 第一步创建xhr对象 v ...

  6. js 深度复制deepClone

    function isObject(obj) { return typeof obj === 'object' && obj != null; } const deepClone =( ...

  7. C#复习笔记(4)--C#3:革新写代码的方式(用智能的编译器来防错)

    用智能的编译器来防错 本章的主要内容: 自动实现的属性:编写由字段直接支持的简单属性, 不再显得臃肿不堪: 隐式类型的局部变量:根据初始值推断类型,简化局部变量的声明: 对象和集合初始化程序:用一个表 ...

  8. PropertyChangeSupport 监听器模式的应用

    PropertyChangeSupport 类实现的监听器功能 ,它是java jdk自带的一个类,用于监听某个对象属性的改变来触发相应信息,具体看代码介绍 import java.beans.Pro ...

  9. tomcat server.xml各个端口的作用

    <Server port="8005" shutdown="SHUTDOWN"> <!-- port:指定一个端口,这个端口负责监听关闭Tom ...

  10. 【知乎】WinForm 与 WPF的区别

    你想上班 那么针对公司需求学如果只是自己写着玩 那么区分一下1.你的程序运行在 自己机器a.一个工具而已 要的是cooooooool 那么WPFb.一个工具而已 要的是useful easy 那么wi ...