题目链接:

[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. 欢迎加入.NET Core 技术QQ群一起讨论交流学习

    群号:4656606 介绍:本群主要讨论.NET Core及其相关技术,如:IdentityServer4.ABP.Dcoker.Linux.Devops.微服务等,如果你正在使用或者准备使用.NET ...

  2. 在GridControl控件上绑定图片的几种操作方式

    我们知道,基于DevExpress的开发Winform的项目界面的时候,GridControl控件是经常用来绑定数据的,一般以常规的字符内容为主,有时候也会有图片的显示需要,那么如果显示图片,我们应该 ...

  3. [翻译] .NET Standard 2.1 公布

    [翻译] .NET Standard 2.1 公布 原文: Announcing .NET Standard 2.1 校对: Cloud 自从大约一年前发布 .NET Standard 2.0以来,我 ...

  4. [翻译] ASP.NET Core 2.1.0 发布

    原文: ASP.NET Core 2.1.0 now available 今天,我们很高兴可以发布 ASP.NET Core 2.1.0!这是我们 .NET平台下开源的.跨平台的 Web 框架的最新版 ...

  5. LeetCode 832. Flipping an Image

    Given a binary matrix A, we want to flip the image horizontally, then invert it, and return the resu ...

  6. Flask序列化

    我们在做后台接口的时候,对于返回值,用的最多的就是json数据格式 flask中,返回json数据格式,我们可以用到flask的jsonify函数. 对于基础序列是可以直接序列化的,但是更多的情况下, ...

  7. vue特殊属性 key ref slot

    1.key 当使用key时,必须设置兄弟元素唯一的key,当key排列顺序变化时,兄弟元素会重新排列,而当key的值变化时,这个元素会被重新渲染. 有相同父元素的子元素必须有独特的 key.重复的 k ...

  8. D2. Great Vova Wall (Version 2)

    l链接 [https://codeforces.com/contest/1092/problem/D2] 题意 和D1一样只是不能竖直放了 分析 水平放的话,就只可能是相邻等时才可以,而且你会发现 只 ...

  9. Azure系列2.1.1 —— BlobContainerPermissions

    (小弟自学Azure,文中有不正确之处,请路过各位大神指正.) 网上azure的资料较少,尤其是API,全是英文的,中文资料更是少之又少.这次由于公司项目需要使用Azure,所以对Azure的一些学习 ...

  10. 修改tomcat控制台title的方法

    修改tomcat控制台title的方法,参考:http://www.jspkongjian.net/news.jsp?id=1125,具体如图: