@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。