@loj - 2339@ 「WC2018」通道
@desription@
11328 年,C 国的科学家们研发了一种高速传送通道,可以在很短的时间内把居民从通道的一端送往另一端,这些通道都是双向的。
美中不足的是,这种传送通道需要进行大量的维护和检修。经过规划,C 国总统决定在 M 城中新建这种通道,在 M 城中,建立了 n 个传送站和 3×(n−1) 条传送通道,这些传送通道被分为 3 组,每一组都包含了 (n−1) 条通道。
当任意一组通道运行时,居民都可以通过这组通道从任意一个传送站前往任意的另一个传送站。也就是说,所有的传送站都会被通道所连通。三组通道按照 1、2、3 的顺序轮流运行,循环反复。在任意一个时刻,都有且只有一组传送通道可以使用。形式化地,在第 i 天中,有且只有第 ((i−1)mod3+1) 组通道运行。
C 国著名科学家 Access Globe 正在进行一项社会调查实验:调查两个传送站之间的传送通道使用者的信息。Access Globe 的计划是这样的:
选定两个传送站 a、b
第一天,他从 a 出发,使用正在运行的这组通道沿最短路径到达 b,并调查经过的所有通道上使用者的信息
第二天,他从 b 出发,使用正在运行的这组通道沿最短路径到达 a,并调查经过的所有通道上使用者的信息
第三天,他从 a 出发,使用正在运行的这组通道沿最短路径到达 b,并调查经过的所有通道上使用者的信息
Access Globe 知道每一条传输线路在运行时的使用者人数。他希望找出一对 a、b,使得在整个实验过程中所有经过的通道的使用者数量之和最大。Access Globe 希望参加 CCF NOI 2018 冬令营的你帮他解决这个简单的小问题。如果你成功地解决了这个问题,Access Globe 会送你一份小礼物——100 分!
输入格式
输入文件的第 1 行包含一个正整数 n,表示传送站的个数,传送站从 1 到 n 编号;
输入文件的第 2 到第 n 行,每行包含 3 个数 u,v,w,表示第一组通道中有一条连接 u,v 的通道,其运行时使用者数量为 w 人;
输入文件的第 (n+1) 到第 (2n−1) 行,每行包含 3 个数 u,v,w,表示第二组通道中有一条连接 u,v 的通道,其运行时使用者数量为 w 人;
输入文件的第 2n 到第 (3n−2) 行,每行包含 3 个数 u,v,w,表示第三组通道中有一条连接 u,v 的通道,其运行时使用者数量为 w 人。
输出格式
输出文件共 1 行,包含一个整数,表示最大的使用者数量之和。
样例一
input
5
1 2 2
1 3 0
1 4 1
4 5 7
1 2 0
2 3 1
2 4 1
2 5 3
1 5 2
2 3 8
3 4 5
4 5 1
output
27
explanation
一种可行的方案是选择 a=2,b=5,这样的使用者数量之和为 (3)+(8+5+1)+(2+1+7)=27。
限制和约定
对于所有数据,2≤n≤105,0≤w≤1012。
@solution@
【题目粘的是 uoj 的因为 loj 的数学公式不好粘】
一年过去怕不是都成了什么模板题/入门题了吧。。。不过放在当年应该算是什么黑科技吧。。。
应该说,可能是因为边分治一点都没有普及开来。。。为什么呢,明明是我先来的是和点分治差不多的算法。。。
好吧进入正题。我们令第一棵树为 t1,第二棵树为 t2,第三棵树为 t3。
题目简单来说:求一对 (u, v) 使得 t1.dis(u, v) + t2.dis(u, v) + t3.dis(u, v) 最大。
题解简单来说:t1 边分治 + t2 虚树 + t3 维护直径。
考虑对 t1 重构然后进行边分治,因为是边权不是点权所以很好重构。
在每一层中,中心边将当前连通块分成两个连通块。我们考虑经过中心边的所有路径 (u, v),记 f[u] 表示点 u 距离中心边的端点的距离,记中心边长度为 val。
则对于两个处在不同连通块的 u, v 有 t1.dis(u, v) = f[u] + val + f[v]。可以发现 val 与 u 和 v 无关,所以我们只需要求出 f[u] + f[v] + t2.dis(u, v) + t3.dis(u, v) 的最大值。
对每一层,在 t2 建出这一层的连通块所对应的虚树。枚举 u 和 v 的 lca 为 p,将问题再度转为 f[u] + f[v] + t2.dep[u] + t2.dep[v] - 2*t2.dep[p] + t3.dis(u, v)。
可以发现 t2.dep[p] 与 u, v 无关,实际上是要求 (f[u] + t2.dep[u]) + (f[v] + t2.dep[v]) + t3.dis(u, v) 的最大值。
我们可以在 t3 中对于每一个点 i 再建一个新点 i',i' 向 i 连 f[i] + t2.dep[i] 的边,则只需要求 t3.dis(u', v')。实际实现上并不需要连这条边,这只是为了方便思考。
有一个结论:对于两个顶点集合 S, T,一个端点在 S 中选择,另一个端点在 T 中选择得到的路径最大值,一定会在 S 中的直径端点和 T 中的直径端点中两两之间的距离中产生。
于是可以通过在 t2 中 dfs 时用类 dp 的方法维护 i 子树中对应的 t1 中两个连通块内的点分别在 t3 中的直径,并维护出答案的最优值即可。维护直径也可以使用上面的结论。
t1 边分治,对于边分治的每一层在 t2 跑虚树,这个地方为时间复杂度的瓶颈 O(nlog^2 n)。
顺便,求 lca 是可以有 O(nlog n) 预处理 O(1) 询问的方法(转为 rmq )。因为这道题需要很多求解距离的地方所以使用这个方法要快些。
@accepted code@
#include<cstdio>
#include<algorithm>
using namespace std;
#define fi first
#define se second
typedef long long ll;
typedef pair<int, int> pii;
const int MAXN = 200000;
int lg[MAXN + 5];
struct Graph{
struct edge{
bool tag;
int to; ll dis;
edge *nxt, *rev;
}edges[2*MAXN + 5], *adj[MAXN + 5], *ecnt;
int dfn[2*MAXN + 5], fir[MAXN + 5], dep[MAXN + 5], dcnt;
int st[20][MAXN + 5]; ll dis[2*MAXN + 5];
Graph() {dcnt = 0, ecnt = &edges[0];}
void addedge(int u, int v, ll w) {
// printf("! %d %d %lld\n", u, v, w);
edge *p = (++ecnt), *q = (++ecnt);
p->to = v, p->dis = w, p->tag = false;
p->nxt = adj[u], adj[u] = p;
q->to = u, q->dis = w, q->tag = false;
q->nxt = adj[v], adj[v] = q;
q->rev = p, p->rev = q;
}
void dfs(int x, int f) {
dep[x] = dep[f] + 1;
dfn[++dcnt] = x, fir[x] = dcnt;
for(edge *p=adj[x];p;p=p->nxt) {
if( p->to == f ) continue;
dis[p->to] = dis[x] + p->dis;
dfs(p->to, x);
dfn[++dcnt] = x;
}
}
void get_st() {
for(int i=1;i<=dcnt;i++)
st[0][i] = dfn[i];
for(int j=1;j<20;j++) {
int t = (1<<(j-1));
for(int i=1;i+t<=dcnt;i++)
st[j][i] = (dep[st[j-1][i]] <= dep[st[j-1][i+t]]) ? st[j-1][i] : st[j-1][i+t];
}
}
void build() {dfs(1, 0), get_st();}
int lca(int u, int v) {
if( fir[u] > fir[v] ) swap(u, v);
int k = lg[fir[v] - fir[u] + 1], l = (1<<k);
return (dep[st[k][fir[u]]] <= dep[st[k][fir[v]-l+1]]) ? st[k][fir[u]] : st[k][fir[v]-l+1];
}
ll dist(int u, int v) {return dis[u] + dis[v] - 2*dis[lca(u, v)];}
}G1, G2, G3, G4;
int n, m;
void rebuild(const Graph &G1, Graph &G2, int x, int f) {
int lst = -1;
for(Graph::edge *p=G1.adj[x];p;p=p->nxt) {
if( p->to == f ) continue;
rebuild(G1, G2, p->to, x);
if( lst == -1 ) {
G2.addedge(x, p->to, p->dis);
lst = x;
}
else {
int s = (++m);
G2.addedge(lst, s, 0);
G2.addedge(s, p->to, p->dis);
lst = s;
}
}
}
void init() {
for(int i=2;i<=MAXN;i++)
lg[i] = lg[i>>1] + 1;
}
int tid[MAXN + 5], dcnt = 0;
void get_tid(Graph &G, int x, int f) {
tid[x] = (++dcnt);
for(Graph::edge *&p=G.adj[x];p;p=p->nxt)
if( p->to != f ) get_tid(G, p->to, x);
}
int siz[MAXN + 5];
bool comp(Graph::edge *a, Graph::edge *b, int tot) {
if( a == NULL ) return false;
if( b == NULL ) return true;
return max(siz[a->to], tot-siz[a->to]) < max(siz[b->to], tot-siz[b->to]);
}
Graph::edge *get_mid_edge(const Graph &G, int x, int f, int tot) {
Graph::edge *ret = NULL; siz[x] = 1;
for(Graph::edge *p=G.adj[x];p;p=p->nxt) {
if( p->to == f || p->tag ) continue;
Graph::edge *tmp = get_mid_edge(G, p->to, x, tot);
siz[x] += siz[p->to];
if( tmp && comp(tmp, ret, tot) )
ret = tmp;
if( comp(p, ret, tot) )
ret = p;
}
return ret;
}
bool cmp(int a, int b) {
return tid[a] < tid[b];
}
int arr[MAXN + 5], stk[MAXN + 5], type[MAXN + 5], acnt = 0, tp = 0, root;
ll dis[MAXN + 5];
void dfs(const Graph &G, int x, int f, int t, ll d) {
if( x <= n )
acnt++, arr[acnt] = x, type[x] = t, dis[x] = d + G2.dis[x];
for(Graph::edge *p=G.adj[x];p;p=p->nxt) {
if( p->to == f || p->tag ) continue;
dfs(G, p->to, x, t, d + p->dis);
}
}
void insert(int x) {
if( !tp ) {
stk[++tp] = x;
return ;
}
int l = G2.lca(stk[tp], x);
if( l == stk[tp] ) {
stk[++tp] = x;
return ;
}
else {
while( true ) {
int x = stk[tp--];
if( tp && tid[stk[tp]] >= tid[l] ) {
G2.addedge(stk[tp], x, G2.dist(stk[tp], x));
if( stk[tp] == l ) break;
}
else {
stk[++tp] = l;
G2.addedge(l, x, G2.dist(l, x));
break;
}
}
stk[++tp] = x;
}
}
void build_vtree(const Graph &G, Graph::edge *m) {
G2.ecnt = &G2.edges[0];
acnt = 0, dfs(G, m->to, 0, -1, 0), dfs(G, m->rev->to, 0, 1, 0);
sort(arr + 1, arr + acnt + 1, cmp);
for(int i=1;i<=acnt;i++)
insert(arr[i]);
root = stk[1];
while( tp ) {
int x = stk[tp--];
if( tp ) G2.addedge(x, stk[tp], G2.dist(x, stk[tp]));
}
}
ll func(const int &a, const int &b, const int &c) {
return dis[a] + dis[b] - 2*G2.dis[c] + G3.dist(a, b);
}
void update2(ll &ans, const pii &a, const pii &b, const int &x) {
ans = max(ans, func(a.fi, b.fi, x)), ans = max(ans, func(a.fi, b.se, x));
ans = max(ans, func(a.se, b.fi, x)), ans = max(ans, func(a.se, b.se, x));
}
void update4(pii &a, const int &b, const int &x) {
ll p = func(a.fi, b, x), q = func(a.se, b, x), r = func(a.fi, a.se, x);
if( p >= q && p >= r ) a.se = b;
else if( q >= p && q >= r ) a.fi = b;
}
void update1(pair<pii, pii>&a, const pair<pii, pii>&b, ll &ans, const int &x) {
if( a.fi.fi ) {
if( b.se.fi ) update2(ans, a.fi, b.se, x);
if( b.fi.fi ) update4(a.fi, b.fi.fi, x), update4(a.fi, b.fi.se, x);
}
else a.fi = b.fi;
if( a.se.fi ) {
if( b.fi.fi ) update2(ans, a.se, b.fi, x);
if( b.se.fi ) update4(a.se, b.se.fi, x), update4(a.se, b.se.se, x);
}
else a.se = b.se;
}
pair<pii, pii>dfs1(int x, int f, ll &ans) {
pair<pii, pii>ret = make_pair(make_pair(0, 0), make_pair(0, 0));
if( type[x] ) {
if( type[x] == 1 ) ret.first = make_pair(x, x);
if( type[x] == -1 ) ret.second = make_pair(x, x);
}
for(Graph::edge *&p=G2.adj[x];p;p=p->nxt) {
if( p->to == f ) continue;
pair<pii, pii>tmp = dfs1(p->to, x, ans);
update1(ret, tmp, ans, x);
}
return ret;
}
ll divide(const Graph &G, int x, int tot) {
Graph::edge *m = get_mid_edge(G, x, 0, tot);
if( m == NULL ) return 0;
m->tag = m->rev->tag = true;
build_vtree(G, m);
ll ans = 0;
dfs1(root, 0, ans); ans += m->dis;
for(int i=1;i<=acnt;i++)
type[arr[i]] = 0;
return max(ans, max(divide(G, m->to, siz[m->to]), divide(G, m->rev->to, tot-siz[m->to])));
}
int main() {
init();
scanf("%d", &n);
for(int i=1;i<n;i++) {
int u, v; ll w;
scanf("%d%d%lld", &u, &v, &w);
G1.addedge(u, v, w);
}
for(int i=1;i<n;i++) {
int u, v; ll w;
scanf("%d%d%lld", &u, &v, &w);
G2.addedge(u, v, w);
}
for(int i=1;i<n;i++) {
int u, v; ll w;
scanf("%d%d%lld", &u, &v, &w);
G3.addedge(u, v, w);
}
m = n, rebuild(G1, G4, 1, 0);
G2.build(), G3.build(), G4.build();
get_tid(G2, 1, 0);
printf("%lld\n", divide(G4, 1, m));
}
@details@
写起来虽然比较长但是实际上并不算难,按照思路写就很轻松地写出来了。
基本也没有怎么 debug。
@loj - 2339@ 「WC2018」通道的更多相关文章
- LOJ 2339 「WC2018」通道——边分治+虚树
题目:https://loj.ac/problem/2339 两棵树的话,可以用 CTSC2018 暴力写挂的方法,边分治+虚树.O(nlogn). 考虑怎么在这个方法上再加一棵树.发现很难弄. 看了 ...
- 「WC2018」通道
没有代码能力... LOJ #2339 Luogu P4220 UOJ #347 题意 给定三棵树$ T1,T2,T3$,求一个点对$ (x,y)$使得$ T1.dist(x,y)+T2.dist(x ...
- loj#2340. 「WC2018」州区划分
FWT&&FMT板子 #include<cstdio> #include<iostream> #include<cstring> #include& ...
- loj2341「WC2018」即时战略(随机化,LCT/动态点分治)
loj2341「WC2018」即时战略(随机化,LCT/动态点分治) loj Luogu 题解时间 对于 $ datatype = 3 $ 的数据,explore操作次数只有 $ n+log n $ ...
- Loj #2192. 「SHOI2014」概率充电器
Loj #2192. 「SHOI2014」概率充电器 题目描述 著名的电子产品品牌 SHOI 刚刚发布了引领世界潮流的下一代电子产品--概率充电器: 「采用全新纳米级加工技术,实现元件与导线能否通电完 ...
- Loj #3096. 「SNOI2019」数论
Loj #3096. 「SNOI2019」数论 题目描述 给出正整数 \(P, Q, T\),大小为 \(n\) 的整数集 \(A\) 和大小为 \(m\) 的整数集 \(B\),请你求出: \[ \ ...
- Loj #3093. 「BJOI2019」光线
Loj #3093. 「BJOI2019」光线 题目描述 当一束光打到一层玻璃上时,有一定比例的光会穿过这层玻璃,一定比例的光会被反射回去,剩下的光被玻璃吸收. 设对于任意 \(x\),有 \(x\t ...
- Loj #3089. 「BJOI2019」奥术神杖
Loj #3089. 「BJOI2019」奥术神杖 题目描述 Bezorath 大陆抵抗地灾军团入侵的战争进入了僵持的阶段,世世代代生活在 Bezorath 这片大陆的精灵们开始寻找远古时代诸神遗留的 ...
- Loj #2542. 「PKUWC2018」随机游走
Loj #2542. 「PKUWC2018」随机游走 题目描述 给定一棵 \(n\) 个结点的树,你从点 \(x\) 出发,每次等概率随机选择一条与所在点相邻的边走过去. 有 \(Q\) 次询问,每次 ...
随机推荐
- 【solr】Solr5.5.4单机部署
Solr5.5.4单机部署 Solr5和Solr4有很大区别,最为明显的就是Solr5已经可以独立部署,从Solr5开始,Solr已经不再以war包形式部署,Solr已经成为了一个独立的java服务端 ...
- Mac Eclipse上Android SDK manager闪退的问题!!
最近想自学一下Android,也没啥人指导,安装的过程中就花了一整天....安装完ADT,安装完SDK,所有步骤都照着网上来,可是一打开SDK manager就闪退!网上所有方法都找了,可是几乎全是w ...
- webpack4进阶配置
移动端CSS px自动转换成rem 需要两步来实现: px2rem-loader 在构建阶段将px转换成rem lib-flexible 页面渲染时动态计算根元素的font-size值(手机淘宝开源库 ...
- NOIP模拟 17.9.28
公交车[问题描述]市内有
- 关于background-image调整大小和位置的方法笔记
遇到background-image的问题有点多,直接上网搜资料自己整理一下 <!DOCTYPE html> <html lang="en"> <he ...
- pip 异常问题
场景:安装的python版本为3.6.4 在使用pip命令时,出现错误:Did not provide a command 如下图所示: 解决办法:pip带上后缀.exe 为什么会出现这种情况? 主要 ...
- 软工作业———Alpha版本第二周小结
姓名 学号 周前计划安排 每周实际工作记录 自我打分 zxl 061425 1.进行任务分配2.实现扫码和生成二维码功能 1.对主要任务进行了划分,但还为进行给模块间的联系2.完成了扫码签到功能 90 ...
- 用Direct2D和DWM来做简单的动画效果2
原文:用Direct2D和DWM来做简单的动画效果2 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/sunnyloves/article/detai ...
- js实现动态计数效果
下面附有数字图片和数字边框图 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> ...
- kubernetes1.4新特性:支持两种新的卷插件
背景介绍 在Kubernetes中卷的作用在于提供给POD持久化存储,这些持久化存储可以挂载到POD中的容器上,进而给容器提供持久化存储. 从图中可以看到结构体PodSpec有个属性是Volumes, ...