LOJ 2553 「CTSC2018」暴力写挂——边分治+虚树
题目:https://loj.ac/problem/2553
第一棵树上的贡献就是链并,转化成 ( dep[ x ] + dep[ y ] + dis( x, y ) ) / 2 ,就可以在第一棵树上写边分治,把两边的点到第二棵树上建虚树,在虚树上 DP ,那么虚树上的当前点就是它不同子树里点的 lca ,所以记 dp[ cr ][ 0/1 ] 表示该点子树里 “第一棵树边分治的两个点集” 里最大的两个贡献;用当前点的深度作为 “第二棵树的 lca 深度” 来更新答案即可。
一直 TLE 。
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
int rdn()
{
int ret=;bool fx=;char ch=getchar();
while(ch>''||ch<''){if(ch=='-')fx=;ch=getchar();}
while(ch>=''&&ch<='')ret=ret*+ch-'',ch=getchar();
return fx?ret:-ret;
}
ll Mx(ll a,ll b){return a>b?a:b;}
ll Mn(ll a,ll b){return a<b?a:b;}
const int N=; const ll INF=1e16;
int n,hd[N],xnt=,to[N<<],nxt[N<<],w[N<<],tw;//xnt=1
int siz[N],mn,Rt,p[N],tot; bool vis[N];
ll dep[N],dis[N],ans;
namespace Tr{
const int K=;
int hd[N],xnt,to[N<<],nxt[N<<],w[N<<];
int dfn[N],tim,sta[N],top,pre[N][K+],bin[K+];
ll dep[N],dp[N][]; int tdp[N];
bool cmp(int u,int v){return dfn[u]<dfn[v];}
void add(int x,int y,int z)
{to[++xnt]=y;nxt[xnt]=hd[x];hd[x]=xnt;w[xnt]=z;}
void ini_dfs(int cr,int fa)
{
dfn[cr]=++tim; dp[cr][]=dp[cr][]=-INF;
tdp[cr]=tdp[fa]+; pre[cr][]=fa;
for(int t=,d=fa;(d=pre[d][t-]);t++)
pre[cr][t]=d;
for(int i=hd[cr],v;i;i=nxt[i])
if((v=to[i])!=fa)
{ dep[v]=dep[cr]+w[i]; ini_dfs(v,cr);}
}
void init()
{
for(int i=,u,v,z;i<n;i++)
{
u=rdn();v=rdn();z=rdn();
add(u,v,z); add(v,u,z);
}
bin[]=;for(int i=;i<=K;i++)bin[i]=bin[i-]<<;
ini_dfs(,);
}
int get_lca(int x,int y)
{
if(tdp[x]<tdp[y])swap(x,y); int d=tdp[x]-tdp[y];
for(int t=;d;t++)
if(d&bin[t])x=pre[x][t],d^=bin[t];
if(x==y)return x;
for(int t=K;t>=;t--)
if(pre[x][t]!=pre[y][t])
x=pre[x][t], y=pre[y][t];
return pre[x][];
}
void link(int cr,int v)
{
ans=Mx(ans,((dp[cr][]+dp[v][]+tw)>>)-dep[cr]);
ans=Mx(ans,((dp[cr][]+dp[v][]+tw)>>)-dep[cr]);
dp[cr][]=Mx(dp[cr][],dp[v][]);
dp[cr][]=Mx(dp[cr][],dp[v][]);
dp[v][]=dp[v][]=-INF;//
}
void solve()
{
sort(p+,p+tot+,cmp);
sta[top=]=;//
for(int i=(p[]==)+;i<=tot;i++)
{
int cr=p[i], lca=get_lca(cr,sta[top]);
while(top&&dfn[lca]<dfn[sta[top]])
{
if(dfn[lca]<dfn[sta[top-]])
link(sta[top-],sta[top]), top--;
else
link(lca,sta[top]), top--;
}
if(sta[top]!=lca)sta[++top]=lca;
sta[++top]=cr;
}
for(int i=top-;i;i--)link(sta[i],sta[i+]);
dp[sta[]][]=dp[sta[]][]=-INF;
}
}
void add(int x,int y,int z){to[++xnt]=y;nxt[xnt]=hd[x];hd[x]=xnt;w[xnt]=z;}
void ini_dfs(int cr,int fa)
{
for(int i=hd[cr],v;i;i=nxt[i])
if((v=to[i])!=fa)
{ dep[v]=dep[cr]+w[i]; ini_dfs(v,cr);}
}
void get_rt(int cr,int fa,int s)
{
siz[cr]=;
for(int i=hd[cr],v;i;i=nxt[i])
if(!vis[i>>]&&(v=to[i])!=fa)
{
get_rt(v,cr,s); siz[cr]+=siz[v];
int tmp=Mx(siz[v],s-siz[v]);
if(tmp<mn) mn=tmp, Rt=i;
}
}
void dfs(int cr,int fa,ll lj,bool fx)
{
Tr::dp[cr][fx]=lj+dep[cr]; p[++tot]=cr;
for(int i=hd[cr],v;i;i=nxt[i])
if(!vis[i>>]&&(v=to[i])!=fa)
dfs(v,cr,lj+w[i],fx);
}
void solve(int cr,int s)
{
vis[cr>>]=; tot=;
dfs(to[cr],,,); dfs(to[cr^],,,);
tw=w[cr]; Tr::solve();
int v=to[cr], ts=siz[v];
if(ts>){ mn=N;get_rt(v,,ts);solve(Rt,ts);}
v=to[cr^]; ts=s-ts;
if(ts>){ mn=N;get_rt(v,,ts);solve(Rt,ts);}
}
int main()
{
n=rdn();
for(int i=,u,v,z;i<n;i++)
{
u=rdn();v=rdn();z=rdn();
add(u,v,z); add(v,u,z);
}
Tr::init(); ini_dfs(,);
mn=N;get_rt(,,n);solve(Rt,n);
for(int i=;i<=n;i++)
ans=Mx(ans,dep[i]-Tr::dep[i]);
printf("%lld\n",ans);
return ;
}
以为是常数不够优秀,所以学习了一下别人的代码。http://blog-wayne.com/2018/05/15/509/
仔细一想,建虚树的时候每次 logn 地找 lca ,这样岂不是 nlog2n 的?所以不要每次建虚树都找 lca 。
一开始把所有 n 个点的相邻点之间的 lca 都找出来。令 a[ i ] 存第一棵树里 dfs 序第 i 大的点的标号, lca[ i ] 存 a[ i ] 和 a[ i-1 ] 的 lca 。
边分治的过程中带一个 l , r ,表示现在整个点集对应第二棵树的 a[ l ] ~ a[ r ] 。那么用 lca[ ] 就可以建虚树了。
考虑当前点集分成两个之后,怎么更新 a[ ] 和 lca[ ] ;
在 a[ ] 中找出所有属于边分治一边的点,假设是 a[ 3 ] , a[ 6 ] , a[ 7 ] ... ,那么 a[ 3 ] 对应的 lca 随便, a[ 6 ] 对应的 lca 应该是 lca[ 4 ] , lca[ 5 ] , lca[ 6 ] 中深度最浅的那个;
这样就能在边分治的过程中维护好 a[ ] 和 lca[ ] 了。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define ll long long
#define pb push_back
#define pii pair<int,int>
#define mkp make_pair
using namespace std;
int rdn()
{
int ret=;bool fx=;char ch=getchar();
while(ch>''||ch<''){if(ch=='-')fx=;ch=getchar();}
while(ch>=''&&ch<='')ret=ret*+ch-'',ch=getchar();
return fx?ret:-ret;
}
ll Mn(ll a,ll b){return a<b?a:b;}
ll Mx(ll a,ll b){return a>b?a:b;}
const int N=<<; const ll INF=1e15;
int n,hd[N],xnt=,to[N<<],nxt[N<<],w[N<<];
int mn,siz[N],Rt; ll ans,dp2[N],dp[N][]; bool lx[N];
vector<pii> vt[N];
namespace Tr{
int hd[N],xnt,to[N<<],nxt[N<<],w[N<<],tim;
int dep[N],a[N],ta[N],lca[N],tlca[N];
int sta[N],top; ll dp2[N];
void add(int x,int y,int z)
{to[++xnt]=y;nxt[xnt]=hd[x];hd[x]=xnt;w[xnt]=z;}
void ini_dfs(int cr,int fa)
{
while(top&&dep[sta[top]]>=dep[cr])top--;
a[++tim]=cr; lca[tim]=sta[top]; sta[++top]=cr;
dp[cr][]=dp[cr][]=-INF;
for(int i=hd[cr],v;i;i=nxt[i])
if((v=to[i])!=fa)
{
dep[v]=dep[cr]+; dp2[v]=dp2[cr]+w[i];
ini_dfs(v,cr);
}
}
void init()
{
for(int i=,u,v,z;i<n;i++)
u=rdn(),v=rdn(),z=rdn(),add(u,v,z),add(v,u,z);
ini_dfs(,);
}
void link(int cr,int v,int tw)
{
ans=Mx(ans,((dp[cr][]+dp[v][]+tw)>>)-dp2[cr]);
ans=Mx(ans,((dp[cr][]+dp[v][]+tw)>>)-dp2[cr]);
dp[cr][]=Mx(dp[cr][],dp[v][]);
dp[cr][]=Mx(dp[cr][],dp[v][]);
dp[v][]=dp[v][]=-INF;
}
int solve(int l,int r,int tw)
{
sta[top=]=a[l];
for(int i=l+;i<=r;i++)
{
int lm=dep[lca[i]];
while(top&&dep[sta[top]]>lm)
if(dep[sta[top-]]>lm)
link(sta[top-],sta[top],tw), top--;
else
link(lca[i],sta[top],tw), top--;
if(sta[top]!=lca[i])sta[++top]=lca[i];
sta[++top]=a[i];
}
for(int i=top-;i;i--)link(sta[i],sta[i+],tw);
dp[sta[]][]=dp[sta[]][]=-INF;
int mid=l-;
for(int i=l,tl=;i<=r;i++)
{
if(!tl||dep[lca[i]]<dep[tl])tl=lca[i];
if(!lx[a[i]])
{ ta[++mid]=a[i]; tlca[mid]=tl; tl=;}
}
int ret=mid;
for(int i=l,tl=;i<=r;i++)
{
if(!tl||dep[lca[i]]<dep[tl])tl=lca[i];
if(lx[a[i]])
{ ta[++mid]=a[i]; tlca[mid]=tl; tl=;}
}
for(int i=l;i<=r;i++)a[i]=ta[i];
for(int i=l;i<=r;i++)lca[i]=tlca[i];
return ret;
}
}
void add(int x,int y,int z)
{
to[++xnt]=y;nxt[xnt]=hd[x];hd[x]=xnt;w[xnt]=z;
to[++xnt]=x;nxt[xnt]=hd[y];hd[y]=xnt;w[xnt]=z;
}
void del_ed(int x,int y)
{
if(to[hd[x]]==y)hd[x]=nxt[hd[x]];
else
{
for(int i=hd[x],pr;i;pr=i,i=nxt[i])
if(to[i]==y)nxt[pr]=nxt[i];
}
if(to[hd[y]]==x)hd[y]=nxt[hd[y]];
else
{
for(int i=hd[y],pr;i;pr=i,i=nxt[i])
if(to[i]==x)nxt[pr]=nxt[i];
}
}
void Rbuild(int cr,int fa)
{
for(int i=,lst=,lm=vt[cr].size();i<lm;i++)
{
int v=vt[cr][i].first, z=vt[cr][i].second;
if(v==fa)continue;
if(!lst)add(cr,v,z),lst=cr;
else{ n++; add(lst,n,); add(n,v,z); lst=n;}
}
for(int i=,v,lm=vt[cr].size();i<lm;i++)
if((v=vt[cr][i].first)!=fa) Rbuild(v,cr);
}
void ini_dfs(int cr,int fa)
{
for(int i=hd[cr],v;i;i=nxt[i])
if((v=to[i])!=fa)
{ dp2[v]=dp2[cr]+w[i]; ini_dfs(v,cr);}
}
void get_rt(int cr,int fa,int s)
{
siz[cr]=;
for(int i=hd[cr],v;i;i=nxt[i])
if((v=to[i])!=fa)
{
get_rt(v=to[i],cr,s); siz[cr]+=siz[v];
int mx=Mx(siz[v],s-siz[v]);
if(mx<mn)mn=mx,Rt=i;
}
}
void dfs(int cr,int fa,ll lj,bool fx)
{
dp[cr][fx]=lj+dp2[cr]; dp[cr][!fx]=-INF; lx[cr]=fx;
for(int i=hd[cr],v;i;i=nxt[i])
if((v=to[i])!=fa) dfs(v,cr,lj+w[i],fx);
}
void solve(int cr,int s,int l,int r)
{
int u=to[cr^], v=to[cr]; del_ed(u,v);
dfs(u,,,); dfs(v,,,);
int mid=Tr::solve(l,r,w[cr]);
int ts=siz[v];
if(ts>){mn=N;get_rt(v,,ts);solve(Rt,ts,mid+,r);}
ts=s-ts;
if(ts>){mn=N;get_rt(u,,ts);solve(Rt,ts,l,mid);}
}
int main()
{
n=rdn();
for(int i=,u,v,z;i<n;i++)
{
u=rdn();v=rdn();z=rdn();
vt[u].pb(mkp(v,z)); vt[v].pb(mkp(u,z));
}
Tr::init(); int yn=n;
Rbuild(,); ini_dfs(,);
mn=N;get_rt(,,n);solve(Rt,n,,yn);
for(int i=;i<=n;i++)
ans=Mx(ans,dp2[i]-Tr::dp2[i]);
printf("%lld\n",ans); return ;
}
后来发现之前 TLE 是忘了写 rebuild ... 不过懒得改了。
LOJ 2553 「CTSC2018」暴力写挂——边分治+虚树的更多相关文章
- Loj #2553. 「CTSC2018」暴力写挂
Loj #2553. 「CTSC2018」暴力写挂 题目描述 temporaryDO 是一个很菜的 OIer .在 4 月,他在省队选拔赛的考场上见到了<林克卡特树>一题,其中 \(k = ...
- LOJ #2533. 「CTSC2018」暴力写挂(边分治合并)
题意 给你两个有 \(n\) 个点的树 \(T, T'\) ,求一对点对 \((x, y)\) 使得 \[ depth(x) + depth(y) - (depth(LCA(x , y)) + dep ...
- BZOJ5341[Ctsc2018]暴力写挂——边分治+虚树+树形DP
题目链接: CSTC2018暴力写挂 题目大意:给出n个点结构不同的两棵树,边有边权(有负权边及0边),要求找到一个点对(a,b)满足dep(a)+dep(b)-dep(lca)-dep'(lca)最 ...
- 「CTSC2018」暴力写挂
毫无$ Debug$能力 全世界就我会被卡空间.jpg LOJ #2553 UOJ #400 Luogu P4565 题意 给定两棵树$ T,T'$,求一组点对$ (x,y)$使得$deep(x)+d ...
- UOJ#400. 【CTSC2018】暴力写挂 边分治 线段树合并
原文链接 www.cnblogs.com/zhouzhendong/p/UOJ400.html 前言 老年选手没有码力. 题解 先对第一棵树进行边分治,然后,设点 x 到分治中心的距离为 $D[x]$ ...
- loj#2552. 「CTSC2018」假面
题目链接 loj#2552. 「CTSC2018」假面 题解 本题严谨的证明了我菜的本质 对于砍人的操作好做找龙哥就好了,blood很少,每次暴力维护一下 对于操作1 设\(a_i\)为第i个人存活的 ...
- 【CTSC2018】暴力写挂(边分治,虚树)
[CTSC2018]暴力写挂(边分治,虚树) 题面 UOJ BZOJ 洛谷 题解 发现第二棵树上的\(LCA\)的深度这玩意没法搞,那么枚举在第二棵树上的\(LCA\). 然后剩下的部分就是\(dep ...
- Loj #2554. 「CTSC2018」青蕈领主
Loj #2554. 「CTSC2018」青蕈领主 题目描述 "也许,我的生命也已经如同风中残烛了吧."小绿如是说. 小绿同学因为微积分这门课,对"连续"这一概 ...
- LOJ #2359. 「NOIP2016」天天爱跑步(倍增+线段树合并)
题意 LOJ #2359. 「NOIP2016」天天爱跑步 题解 考虑把一个玩家的路径 \((x, y)\) 拆成两条,一条是 \(x\) 到 \(lca\) ( \(x, y\) 最近公共祖先) 的 ...
随机推荐
- L320 What late-night snacking has to do with sleep
What late-night snacking has to do with sleep Light-night snacking isn’t just something college stud ...
- day 33 线程池有关的
# cpu 的核心数# import os# print(os.cpu_count()) ## 爬虫的进程和线程的应用# 第一步 虚拟一个浏览器下载 在cmd 里输入 pip install requ ...
- 初识Linux------文件管理
初识Linux------文件管理 说明 由于本章的命令比较多,先对命令有一个整体的说明 命令的一般格式:命令名[选项][参数1][参数2]…… 命令名由小写的英文字母构成,往往是表示相应功能的英文单 ...
- 2010年腾讯前端面试题学习(jquery,html,css部分)
看了牛人写的回忆文章,里面有2010年腾讯的前端面试题,里面涉及到不少基础性的问题,js部分已学习,这是jquery,html和css部分,学习一下:) 原文地址:https://segmentfau ...
- python列举django中间件的5个请求方法
process——request:请求进来时,权限认证. process——view:路由匹配之后,能够得到试图的试图函数. process——exceptions:异常是执行. process——t ...
- Python基础1--Python的安装
一.python的安装 Windows: 1) https://www.python.org/下载 2) 傻瓜式安装 3) 设置python环境变量 计算机-属性-高级系统设置-环境变量,将C:\Py ...
- poj 1236 强联通分量
大致题意给你有一个点数为n<=100的有向图. 求解两个子任务: 1:最少给多少个点信息,这些点的信息可以顺着有向边传遍全图. 2:最少要加多少条边,使得整个图强联通. 求强联通分量再缩点后得到 ...
- about Version Control(版本控制)
what: 版本控制系统是一种软件,它可以帮助您跟踪代码随时间的变化. 在编辑代码时,您告诉版本控制系统对文件进行快照. 版本控制系统将永久保存该快照,以便在以后需要时可以收回它. 如果没有版本控制, ...
- s21day01 python笔记
s21day01 python笔记 一.计算机基础 计算机的初步认识 用户:人 软件:QQ.浏览器等 解释器/编译器/虚拟机:java解释器.python解释器等 操作系统 硬件:CPU.内存.硬盘. ...
- Python的函数基础
引子 现在老板让你写一个监控程序,24小时全年无休的监控你们公司网站服务器的系统状况,当cpu\memory\disk等指标的使用量超过阀值时即发邮件报警,你掏空了所有的知识量,写出了以下代码 whi ...