题目链接:

CSTC2018暴力写挂

题目大意:给出n个点结构不同的两棵树,边有边权(有负权边及0边),要求找到一个点对(a,b)满足dep(a)+dep(b)-dep(lca)-dep'(lca)最大,其中dep为第一棵树中的深度,dep'为第二棵树中的深度,lca为两点的最近公共祖先。注意:a与b可以相同!

本题讲解两种做法,其中第一种做法常数较小且比较好写,第二种做法思路比较奇特。为了方便讲解,设两点在第一棵树中的距离为$dis(x,y)$

解法一

题中给的式子显然不能直接做,我们将它变换一下(设答案的点对为(x,y)):

$dep(x)+dep(y)-dep(lca)-dep'(lca)$

$=\frac{1}{2}(dep(x)+dep(y)-2*dep(lca)+dep(x)+dep(y))-dep'(lca)$

$=\frac{1}{2}(dis(x,y)+dep(x)+dep(y))-dep'(lca)$

我们对第一棵树多叉树转二叉树(详见边分治讲解)然后进行边分治。

对于每次分治,设一个点到分治中心边的距离为$d(x)$,那么式子就变成了$\frac{1}{2}(d(x)+d(y)+value+dep(x)+dep(y))-dep'(lca)$,其中$value$为分治中心边的边权。

我们将一个点在第二棵树上的点权看成是$v(x)=d(x)+dep(x)$,答案就是$v(x)+v(y)+value-dep'(lca)$。

对于每次边分治将分治联通块内所有点在第二棵树上的建出虚树,同时将分治联通块以分治中心边为界限分成两部分,将一部分的点标为黑点,将另一部分的点标为白点。

那么对于虚树中的一个点,以它为$lca$的最大答案就是在它的两个不同子树中分别选出一个黑点和一个白点使这两个点的点权和最大。

我们在虚树上进行树形DP,每个点维护这个点在虚树上的子树中黑点的最大点权及白点的最大点权。

对于每个点回溯时分别将它每个子树的信息合并上来并在合并时更新答案,这样就保证了当前点一定是所维护的最大点权黑点和最大点权白点的lca。

注意求$LCA$时不能比较带权深度而要比较不带权深度。

#include<set>
#include<map>
#include<queue>
#include<cmath>
#include<stack>
#include<cstdio>
#include<vector>
#include<bitset>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
#define INF (1<<30)
#define pr pair<int,ll>
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,f=1; char c=nc(); while(!isdigit(c)) {if(c=='-') f=-1; c=nc();} while(isdigit(c)) x=(((x<<2)+x)<<1)+(c^48),c=nc(); return x*f;}
ll rd2() {ll x=0,f=1; char c=nc(); while(!isdigit(c)) {if(c=='-') f=-1; c=nc();} while(isdigit(c)) x=(((x<<2)+x)<<1)+(c^48),c=nc(); return x*f;}
int n,m;
int x,y;
ll ans;
ll z;
int cnt;
int col[400010];
struct miku
{int x;ll val;
}t[400010];
namespace virtual_tree
{ll res;int tot;int top;int dfn=0;ll mid_edge;ll d[400010];ll v[400010];int s[400010];ll val[800010];int st[400010];int to[800010];int lg[800010];int vis[400010];int dep[400010];int head[400010];int next[800010];int dp[400010][2];int f[800010][20];vector<int>q[400010];inline void add(int x,int y,ll z){next[++tot]=head[x];head[x]=tot;to[tot]=y;val[tot]=z;}inline bool cmp(const miku &a,const miku &b){return s[a.x]<s[b.x];}void dfs(int x,int fa){f[++dfn][0]=x;s[x]=dfn;for(int i=head[x];i;i=next[i]){if(to[i]!=fa){dep[to[i]]=dep[x]+1;d[to[i]]=d[x]+val[i];dfs(to[i],x);f[++dfn][0]=x;}}}inline int mn(int x,int y){return dep[x]<dep[y]?x:y;}inline void ST(){for(int i=2;i<=dfn;i++){lg[i]=lg[i>>1]+1;}for(int j=1;j<=19;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]);}}}inline 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]);}inline void insert(int x){int fa=lca(x,st[top]);if(!vis[fa]){vis[fa]=1;v[fa]=-1ll<<60;dp[fa][0]=dp[fa][1]=0;}while(top>1&&dep[st[top-1]]>=dep[fa]){q[st[top-1]].push_back(st[top]);top--;}if(fa!=st[top]){q[fa].push_back(st[top]);st[top]=fa;}st[++top]=x;}inline int merge(int x,int y){if(!x||!y){return x+y;}return v[x]>v[y]?x:y;}inline void query(int x,int y){if(!x||!y){return ;}res=max(res,v[x]+v[y]);}void tree_dp(int x){int len=q[x].size();for(int i=0;i<len;i++){int to=q[x][i];tree_dp(to);res=-1ll<<60;query(dp[x][0],dp[to][1]);query(dp[x][1],dp[to][0]);ans=max(ans,res+mid_edge-2*d[x]);dp[x][0]=merge(dp[x][0],dp[to][0]);dp[x][1]=merge(dp[x][1],dp[to][1]);}vis[x]=0;q[x].clear();}inline void build(ll value){mid_edge=value;for(int i=1;i<=cnt;i++){vis[t[i].x]=1;v[t[i].x]=t[i].val;dp[t[i].x][col[t[i].x]-1]=t[i].x;dp[t[i].x][(col[t[i].x]-1)^1]=0;col[t[i].x]=0;}sort(t+1,t+1+cnt,cmp);top=0;if(t[1].x!=1){st[++top]=1;}for(int i=1;i<=cnt;i++){insert(t[i].x);}while(top>1){q[st[top-1]].push_back(st[top]);top--;}tree_dp(1);}inline void work(){dfs(1,0);ST();}
};
namespace edge_partation
{int tot;int num;int root;ll d[800010];ll val[1600010];int to[1600010];int vis[800010];int head[800010];int size[800010];int next[1600010];vector<pr>v[400010];inline void push(int x,int y,ll z){v[x].push_back(make_pair(y,z));}inline void add(int x,int y,ll z){next[++tot]=head[x];head[x]=tot;to[tot]=y;val[tot]=z;}void rebuild(int x,int fa){int tmp=0;int last=0;int len=v[x].size();for(int i=0;i<len;i++){int to=v[x][i].first;int val=v[x][i].second;if(to==fa){continue;}tmp++;if(tmp==1){add(x,to,val);add(to,x,val);last=x;}else if(tmp==len-(x!=1)){add(last,to,val);add(to,last,val);}else{m++;add(last,m,0);add(m,last,0);last=m;add(m,to,val);add(to,m,val);}}for(int i=0;i<len;i++){if(v[x][i].first==fa){continue;}rebuild(v[x][i].first,x);}}void dfs(int x,int fa){for(int i=head[x];i;i=next[i]){if(to[i]!=fa){d[to[i]]=d[x]+val[i];dfs(to[i],x);}}}void getroot(int x,int fa,int sum){size[x]=1;for(int i=head[x];i;i=next[i]){if(!vis[i>>1]&&to[i]!=fa){getroot(to[i],x,sum);size[x]+=size[to[i]];int mx_size=max(size[to[i]],sum-size[to[i]]);if(mx_size<num){num=mx_size;root=i;}}}}void dfs2(int x,int fa,ll dep,int opt){if(x<=n){col[x]=opt;t[++cnt]=(miku){x,d[x]+dep};}for(int i=head[x];i;i=next[i]){if(!vis[i>>1]&&to[i]!=fa){dfs2(to[i],x,dep+val[i],opt);}}}void partation(int x,int sum){num=INF;getroot(x,0,sum);if(num==INF){return ;}int now=root;vis[now>>1]=1;cnt=0;dfs2(to[now],0,0ll,1);dfs2(to[now^1],0,0ll,2);virtual_tree::build(val[now]);int sz=size[to[now]];partation(to[now],sz);partation(to[now^1],sum-sz);}inline void work(){tot=1;rebuild(1,0);dfs(1,0);partation(1,m);}
};
int main()
{m=n=rd();ans=-1ll<<60;for(int i=1;i<n;i++){x=rd(),y=rd(),z=rd2();edge_partation::push(x,y,z);edge_partation::push(y,x,z);}for(int i=1;i<n;i++){x=rd(),y=rd(),z=rd2();virtual_tree::add(x,y,z);virtual_tree::add(y,x,z);}virtual_tree::work();edge_partation::work();ans>>=1;for(int i=1;i<=n;i++){ans=max(ans,edge_partation::d[i]-virtual_tree::d[i]);}printf("%lld",ans);
}

解法二

同样将原式转化一下:

$dep(x)+dep(y)-dep(lca)-dep'(lca)$

$=dep(y)+(dep(x)-dep(lca))-dep'(lca)$(这好像除了加了个括号、换了下位置啥都没变啊QAQ)

咳咳...我们先不关注这个,同样对于第一棵树进行边分治,对于一次分治,将当前分治联通块以分治中心边为界分成了两部分,我们设这两部分为$S$和$T$。

如果每次边分治时都以当前联通块在原树中深度最小的点为根找分治中心边,那么分治中心边就是一条从原树的父节点连向子节点的边,那么$S$和$T$中就一定有一个是原树的一棵子树。

我们假设$T$是原树的一棵子树,那么显然$S$距离根节点(即$1$号点)更近。

假设$x\subseteq S,y\subset T,y'\subseteq T$,那么$x$与$y$的$lca$一定不在$T$中,也就是说$lca(x,y)$一定等于$lca(x,y')$。

所以对于$S$中的一个点$x$,它与$T$中任意一个点的$lca$都是相同的,假设分治中心边属于$T$的端点为$v$,那么只要求出$lca(x,v)$就求出了$x$与任意$T$中节点的$lca$。

那么对于$S$中的点,我们将它在第二棵树中的点权设为$v(x)=dep(x)-dep(lca(x,v))$;

对于$T$中的点,我们将它在第二棵树中的点权设为$v(x)=dep(x)$。

再像解法一那样对于分治联通块在第二棵树中建出虚树并在虚树上树形DP即可。

#include<set>
#include<map>
#include<queue>
#include<cmath>
#include<stack>
#include<cstdio>
#include<vector>
#include<bitset>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
#define INF (1<<30)
#define pr pair<int,ll>
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,f=1; char c=nc(); while(!isdigit(c)) {if(c=='-') f=-1; c=nc();} while(isdigit(c)) x=(((x<<2)+x)<<1)+(c^48),c=nc(); return x*f;}
ll rd2() {ll x=0,f=1; char c=nc(); while(!isdigit(c)) {if(c=='-') f=-1; c=nc();} while(isdigit(c)) x=(((x<<2)+x)<<1)+(c^48),c=nc(); return x*f;}
int n,m;
int x,y;
ll ans;
ll z;
int cnt;
int col[400010];
struct miku
{int x;ll val;
}t[400010];
namespace virtual_tree
{ll res;int tot;int top;int dfn=0;ll d[400010];ll v[400010];int s[400010];ll val[800010];int st[400010];int to[800010];int lg[800010];int vis[400010];int dep[400010];int head[400010];int next[800010];int dp[400010][2];int f[800010][20];vector<int>q[400010];inline void add(int x,int y,ll z){next[++tot]=head[x];head[x]=tot;to[tot]=y;val[tot]=z;}inline bool cmp(const miku &a,const miku &b){return s[a.x]<s[b.x];}inline void dfs(int x,int fa){f[++dfn][0]=x;s[x]=dfn;for(int i=head[x];i;i=next[i]){if(to[i]!=fa){dep[to[i]]=dep[x]+1;d[to[i]]=d[x]+val[i];dfs(to[i],x);f[++dfn][0]=x;}}}inline int mn(int x,int y){return dep[x]<dep[y]?x:y;}inline void ST(){for(int i=2;i<=dfn;i++){lg[i]=lg[i>>1]+1;}for(int j=1;j<=19;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]);}}}inline 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]);}inline void insert(int x){int fa=lca(x,st[top]);if(!vis[fa]){vis[fa]=1;v[fa]=-1ll<<60;dp[fa][0]=dp[fa][1]=0;}while(top>1&&dep[st[top-1]]>=dep[fa]){q[st[top-1]].push_back(st[top]);top--;}if(fa!=st[top]){q[fa].push_back(st[top]);st[top]=fa;}st[++top]=x;}inline int merge(int x,int y){if(!x||!y){return x+y;}return v[x]>v[y]?x:y;}inline void query(int x,int y){if(!x||!y){return ;}res=max(res,v[x]+v[y]);}inline void tree_dp(int x){int len=q[x].size();for(int i=0;i<len;i++){int to=q[x][i];tree_dp(to);res=-1ll<<60;query(dp[x][0],dp[to][1]);query(dp[x][1],dp[to][0]);ans=max(ans,res-d[x]);dp[x][0]=merge(dp[x][0],dp[to][0]);dp[x][1]=merge(dp[x][1],dp[to][1]);}vis[x]=0;q[x].clear();}inline void build(){for(int i=1;i<=cnt;i++){vis[t[i].x]=1;v[t[i].x]=t[i].val;dp[t[i].x][col[t[i].x]-1]=t[i].x;dp[t[i].x][(col[t[i].x]-1)^1]=0;col[t[i].x]=0;}sort(t+1,t+1+cnt,cmp);top=0;if(t[1].x!=1){st[++top]=1;}for(int i=1;i<=cnt;i++){insert(t[i].x);}while(top>1){q[st[top-1]].push_back(st[top]);top--;}tree_dp(1);}inline void work(){dfs(1,0);ST();}
};
namespace edge_partation
{int tot;int num;int root;int dfn=0;ll d[800010];int s[800010];int lg[1600010];ll val[1600010];int to[1600010];int vis[800010];int dep[800010];int head[800010];int size[800010];int next[1600010];int f[1600010][21];vector<pr>v[400010];inline void push(int x,int y,ll z){v[x].push_back(make_pair(y,z));}inline void add(int x,int y,ll z){next[++tot]=head[x];head[x]=tot;to[tot]=y;val[tot]=z;}inline void rebuild(int x,int fa){int tmp=0;int last=0;int len=v[x].size();for(int i=0;i<len;i++){int to=v[x][i].first;int val=v[x][i].second;if(to==fa){continue;}tmp++;if(tmp==1){add(x,to,val);add(to,x,val);last=x;}else if(tmp==len-(x!=1)){add(last,to,val);add(to,last,val);}else{m++;add(last,m,0);add(m,last,0);last=m;add(m,to,val);add(to,m,val);}}for(int i=0;i<len;i++){if(v[x][i].first==fa){continue;}rebuild(v[x][i].first,x);}}inline void dfs(int x,int fa){f[++dfn][0]=x;s[x]=dfn;for(int i=head[x];i;i=next[i]){if(to[i]!=fa){dep[to[i]]=dep[x]+1;d[to[i]]=d[x]+val[i];dfs(to[i],x);f[++dfn][0]=x;}}}inline int mn(int x,int y){return dep[x]<dep[y]?x:y;}inline void ST(){for(int i=2;i<=dfn;i++){lg[i]=lg[i>>1]+1;}for(int j=1;j<=20;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]);}}}inline 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]);}inline void getroot(int x,int fa,int sum){size[x]=1;for(int i=head[x];i;i=next[i]){if(!vis[i>>1]&&to[i]!=fa){getroot(to[i],x,sum);size[x]+=size[to[i]];int mx_size=max(size[to[i]],sum-size[to[i]]);if(mx_size<num){num=mx_size;root=i;}}}}inline void dfs2(int x,int fa,int rt,int opt){if(x<=n){col[x]=opt;ll value=rt?d[x]-d[lca(x,rt)]:d[x];t[++cnt]=(miku){x,value};}for(int i=head[x];i;i=next[i]){if(!vis[i>>1]&&to[i]!=fa){dfs2(to[i],x,rt,opt);}}}inline void partation(int x,int sum){num=INF;getroot(x,0,sum);if(num==INF){return ;}int now=root;vis[now>>1]=1;cnt=0;dfs2(x,0,to[now],1);dfs2(to[now],0,0,2);virtual_tree::build();int sz=size[to[now]];partation(to[now],sz);partation(x,sum-sz);}inline void work(){tot=1;rebuild(1,0);dfs(1,0);ST();partation(1,m);}
};
int main()
{m=n=rd();ans=-1ll<<60;for(int i=1;i<n;i++){x=rd(),y=rd(),z=rd2();edge_partation::push(x,y,z);edge_partation::push(y,x,z);}for(int i=1;i<n;i++){x=rd(),y=rd(),z=rd2();virtual_tree::add(x,y,z);virtual_tree::add(y,x,z);}virtual_tree::work();edge_partation::work();for(int i=1;i<=n;i++){ans=max(ans,edge_partation::d[i]-virtual_tree::d[i]);}printf("%lld",ans);
}

转载于:https://www.cnblogs.com/Khada-Jhin/p/10178176.html

BZOJ5341[Ctsc2018]暴力写挂——边分治+虚树+树形DP相关推荐

  1. [WC2018]通道——边分治+虚树+树形DP

    题目链接: [WC2018]通道 题目大意:给出三棵n个节点结构不同的树,边有边权,要求找出一个点对(a,b)使三棵树上这两点的路径权值和最大,一条路径权值为路径上所有边的边权和. 我们按照部分分逐个 ...

  2. [CTSC2018]暴力写挂

    题目 边分治+虚树=双倍的快乐 这个柿子里有两个\(LCA\),我们考虑魔改一下前面的\(\operatorname{depth(x)+depth(y)-depth(LCA(x,y))}\),为了方便 ...

  3. BZOJ 3611 [Heoi2014]:虚树+树形DP

    时空隧道 今天考试T3正解是虚树-..(看到这个名字我好虚啊-.) 现在我们需要处理一棵树上k个点的询问,做一遍树形DP-- 复杂度是O(n)的,q个询问,感觉复杂度很爆炸-..>_<-- ...

  4. BZOJ3572 [Hnoi2014]世界树 【虚树 + 树形dp】

    题目 世界树是一棵无比巨大的树,它伸出的枝干构成了整个世界.在这里,生存着各种各样的种族和生灵,他们共同信奉着绝对公正公平的女神艾莉森,在他们的信条里,公平是使世界树能够生生不息.持续运转的根本基石. ...

  5. BZOJ 2286 消耗战 (虚树+树形DP)

    给出一个n节点的无向树,每条边都有一个边权,给出m个询问, 每个询问询问ki个点,问切掉一些边后使得这些顶点无法与顶点1连接. 最少的边权和是多少. (n<=250000,sigma(ki)&l ...

  6. BZOJ5419[Noi2018]情报中心——线段树合并+虚树+树形DP

    题目链接: [NOI2018]情报中心 题目大意:给出一棵n个节点的树,边有非负边权,并给出m条链,对于每条链有一个代价,要求选出两条有公共边的链使两条链的并的边权和-两条链的代价和最大. 花了一天的 ...

  7. bzoj 3611: [Heoi2014]大工程(虚树+树形DP)

    3611: [Heoi2014]大工程 Time Limit: 60 Sec  Memory Limit: 512 MB Submit: 1697  Solved: 718 [Submit][Stat ...

  8. P2495 [SDOI2011]消耗战-虚树+树形dp

    https://www.luogu.com.cn/problem/P2495 虚树:当我们在解决树形dp的问题的时候,题目中会给出一些询问,询问涉及的关键节点不多,并保证总的点数规模的时候,我们就可以 ...

  9. LOJ 2339 「WC2018」通道——边分治+虚树

    题目:https://loj.ac/problem/2339 两棵树的话,可以用 CTSC2018 暴力写挂的方法,边分治+虚树.O(nlogn). 考虑怎么在这个方法上再加一棵树.发现很难弄. 看了 ...

最新文章

  1. EOS账户系统(2)账户和钱包
  2. java程会释放锁join_关于join() 是否会释放锁的一些思考
  3. 命运2服务器维护时间2019,《命运2》今晚将停机维护 为多平台共用存档做准备...
  4. 学习笔记-工业相机与普通相机优劣利弊
  5. discuz设置用户每天回帖数_如何用Redis统计独立用户访问量,除了Hash跟Bitset,还有这个...
  6. 待支付取件费用是什么意思_SEDEX验厂是什么意思,sedex验厂审核费用是多少?...
  7. .Net 面试题 汇总(二)
  8. mysql 查询缓存及设置
  9. MySQL备份还原——mysqldump工具介绍
  10. cpc客户端紫屏问题解决方法
  11. HG_REPMGR configure配置
  12. matlab仿真电子秤,基于51单片机电子秤的代码
  13. 新一代天气雷达信息共享平台
  14. 通过硅靶摄像管采集图像的电子显微镜
  15. 手机电脑怎么上P站-国内版pixiv你可知晓
  16. seLinux的管理
  17. 电子邮票出售面临着两大困境
  18. 小米air2se耳机只有一边有声音怎么办_2020高颜值游戏低延迟蓝牙耳机,高音质听声辨位非这五款蓝牙耳机莫属...
  19. SAP ABAP——SMARTFORMS(一)【SF概要及文本编辑器】
  20. 计算机应用 pdf,计算机应用70546.pdf

热门文章

  1. 【动手学深度学习】之 现代卷积神经网络 AlexNet VGGNet NIN 习题解答
  2. 创建网页实现上传文件(简明教程)
  3. SpriteKit从零开始~Constraints and Actions
  4. 【安富莱】【RL-TCPnet网络教程】第8章 RL-TCPnet网络协议栈移植(RTX)
  5. 「正定矩阵」和「半正定矩阵」
  6. android 模拟器使用
  7. 秋招季,写份亮眼的求职简历
  8. postgresql立式版本下载_PostgreSQL|PostgreSQL v9.6.12 For Linux下载_网站源码_站长下载
  9. VS code创建Vue项目 方法1:create+项目
  10. 光立方原理讲解_ARCore HDR 光估测深度解析