@description@

定义函数 f(G, x, y) 为 G 中点 x 和点 y 之间的所有路径的权重(该路径上各边权的最大值)的最小值,其中 G 为一个有边权的无向连通图。

给定两个 N 个点 M 条边连通图 G1 和 G2。请你计算:

\[S = \sum_{i=1}^{N-1}\sum_{j=i+1}^{B}f(G1, i, j)*f(G2, i, j) \mod 998244353
\]

输入格式

输入的第一行包含两个整数 N 和 M。

接下来的 M 行每行包含三个整数 u,v 和 w,表示 G1 中的点 u 和 v 由一条权重为 w 的边连接。

再接下来的 M 行每行包含三个整数 u,v 和 w,表示 G2 中的点 u 和 v 由一条权重为 w 的边连接。

输出格式

对于每组数据,输出一行包含一个整数,表示 S 对 998244353 取模的结果。

**数据范围 **

• 1 ≤ N ≤ 10^5

• M = 2N

• 1 ≤ u,v ≤ N

• 1 ≤ w ≤ 10^8

• G1 和 G2 都是连通图

样例数据

输入

3 6

1 2 3

2 3 1

3 1 2

1 2 4

2 3 5

3 1 6

1 2 2

2 3 1

3 1 3

1 2 5

2 3 4

3 1 6

输出

9

@solution@

考虑先分别建出两个图 G1、G2 的 kruskal 重构树 T1、T2,则问题变为:

\[S = \sum_{i=1}^{N-1}\sum_{j=i+1}^{N}T1.key(T2.lca(i, j))*T2.key(T2.lca(i, j))
\]

求两棵树 lca 的权值乘积的和实际上是边分树合并的经典套路。

我们考虑一遍边分治(因为 kruskal 重构树本身是二叉树,所以不用重构)建出边分树,左儿子存储中心边深度较小的那边连通块,右儿子存储中心边深度较大的那边连通块。

于是跨越中心边 (m1, m2) 的路径 (u, v) 的 lca 只跟深度较小的那块连通块有关,不妨记 u 是深度较小的,则 lca(u, v) = lca(u, m1)。不妨将 u 对应的 T1.key(lca(u, m1)) 存储下来记作 f[u]。

枚举 T2.lca(i, j) 为 p 算出对应的 T1.key 之和。考虑将 p 的左右儿子的边分树合并得到 p 的边分树,同时统计答案。

考虑在边分树上维护深度较小那边所有点 f 之和 sum,维护深度较大那边点的数量 cnt。则合并时用两棵边分树的 sum 和 cnt 两两相乘求和就是我们想要得到的东西。

@accepted code@

#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 200000;
const int MOD = 998244353;
int lg[2*MAXN + 5];
struct Graph{
struct edge{
int to; bool tag;
edge *nxt, *rev;
}edges[2*MAXN + 5], *adj[MAXN + 5], *ecnt;
Graph() {ecnt = &edges[0];}
void addedge(int u, int v) {
edge *p = (++ecnt), *q = (++ecnt);
p->to = v, p->nxt = adj[u], adj[u] = p, p->tag = false;
q->to = u, q->nxt = adj[v], adj[v] = q, q->tag = false;
p->rev = q, q->rev = p;
}
int dep[MAXN + 5], dfn[2*MAXN + 5], fir[MAXN + 5], dcnt;
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;
dfs(p->to, x), dfn[++dcnt] = x;
}
}
int st[20][2*MAXN + 5];
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(int x) {dcnt = 0; dfs(x, 0); get_st();}
int lca(int x, int y) {
if( fir[x] > fir[y] ) swap(x, y);
x = fir[x], y = fir[y];
int k = lg[y-x+1], l = (1<<k);
return (dep[st[k][x]] < dep[st[k][y-l+1]]) ? st[k][x] : st[k][y-l+1];
}
}G1, G2;
int siz[MAXN + 5];
bool cmp(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(int x, int f, int tot) {
Graph::edge *ret = NULL; siz[x] = 1;
for(Graph::edge *p=G1.adj[x];p;p=p->nxt) {
if( p->tag || p->to == f ) continue;
Graph::edge *tmp = get_mid(p->to, x, tot);
siz[x] += siz[p->to];
if( cmp(tmp, ret, tot) ) ret = tmp;
if( cmp(p, ret, tot) ) ret = p;
}
return ret;
}
int ch[2][MAXN + 5], etot = 0;
bool dir[32][MAXN + 5]; int key[32][MAXN + 5];
int a[MAXN + 5], b[MAXN + 5], N, M;
void dfs(const int &k, int x, int f, bool t, const int &dep) {
dir[dep][x] = t;
if( !t ) key[dep][x] = a[G1.lca(k, x)];
for(Graph::edge *p=G1.adj[x];p;p=p->nxt) {
if( p->tag || p->to == f ) continue;
dfs(k, p->to, x, t, dep);
}
}
int divide(int x, int tot, int dep) {
Graph::edge *m = get_mid(x, 0, tot);
if( m == NULL ) return -1;
m->tag = m->rev->tag = true;
int tmp = (++etot);
dfs(m->to, m->to, 0, G1.dep[m->to]>G1.dep[m->rev->to], dep);
dfs(m->rev->to, m->rev->to, 0, G1.dep[m->to]<G1.dep[m->rev->to], dep);
ch[G1.dep[m->to]>G1.dep[m->rev->to]][tmp] = divide(m->to, siz[m->to], dep + 1);
ch[G1.dep[m->to]<G1.dep[m->rev->to]][tmp] = divide(m->rev->to, tot-siz[m->to], dep + 1);
return tmp;
}
struct edge{
int u, v, w;
friend bool operator < (edge a, edge b) {
return a.w < b.w;
}
}e[MAXN + 5];
int fa[MAXN + 5];
int find(int x) {
return fa[x] = (fa[x] == x) ? x : find(fa[x]) ;
}
struct node{
node *ch[2];
int cnt, sum;
}nd[32*MAXN + 5], *rt[MAXN + 5], *ncnt, *NIL;
node *new_tree(int nw, int x, int dep) {
if( nw == -1 ) return NIL;
node *p = (++ncnt);
if( !dir[dep][x] ) p->sum = (p->sum + key[dep][x])%MOD;
else p->cnt = (p->cnt + 1)%MOD;
p->ch[dir[dep][x]] = new_tree(ch[dir[dep][x]][nw], x, dep + 1);
p->ch[!dir[dep][x]] = NIL;
return p;
}
int ans = 0, res = 0;
node *merge(node *rt1, node *rt2) {
if( rt1 == NIL ) return rt2;
if( rt2 == NIL ) return rt1;
res = (res + 1LL*rt1->cnt*rt2->sum%MOD) % MOD;
res = (res + 1LL*rt1->sum*rt2->cnt%MOD) % MOD;
rt1->sum = (rt1->sum + rt2->sum)%MOD;
rt1->cnt = (rt1->cnt + rt2->cnt)%MOD;
rt1->ch[0] = merge(rt1->ch[0], rt2->ch[0]);
rt1->ch[1] = merge(rt1->ch[1], rt2->ch[1]);
return rt1;
}
void dfs2(int x, int f) {
if( x <= N )
rt[x] = new_tree(1, x, 0);
else rt[x] = NIL;
for(Graph::edge *p=G2.adj[x];p;p=p->nxt) {
if( p->to == f ) continue;
dfs2(p->to, x);
}
res = 0;
for(Graph::edge *p=G2.adj[x];p;p=p->nxt) {
if( p->to == f ) continue;
rt[x] = merge(rt[x], rt[p->to]);
}
ans = (ans + 1LL*res*b[x]%MOD)%MOD;
}
void init() {
for(int i=2;i<=2*MAXN;i++)
lg[i] = lg[i>>1] + 1;
ncnt = NIL = &nd[0];
NIL->ch[0] = NIL->ch[1] = NIL;
NIL->cnt = NIL->sum = 0;
}
int main() {
init();
scanf("%d%d", &N, &M);
for(int i=1;i<=M;i++)
scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w);
sort(e + 1, e + M + 1);
for(int i=1;i<=N;i++)
fa[i] = i;
int cnt = N;
for(int i=1;i<=M;i++) {
int fu = find(e[i].u), fv = find(e[i].v);
if( fu != fv ) {
a[++cnt] = e[i].w; fa[cnt] = cnt;
fa[fu] = fa[fv] = cnt;
G1.addedge(cnt, fu), G1.addedge(cnt, fv);
}
}
G1.build(find(1)); divide(1, cnt, 0);
for(int i=1;i<=M;i++)
scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w);
sort(e + 1, e + M + 1);
for(int i=1;i<=N;i++)
fa[i] = i;
cnt = N;
for(int i=1;i<=M;i++) {
int fu = find(e[i].u), fv = find(e[i].v);
if( fu != fv ) {
b[++cnt] = e[i].w;
fa[fu] = fa[fv] = fa[cnt] = cnt;
G2.addedge(cnt, fu), G2.addedge(cnt, fv);
}
}
dfs2(find(1), 0);
printf("%d\n", ans);
}

@details@

求 lca 时写了个 st 表求 rmq,然而 st 表需要二倍长度,然而我用的是一倍长度。。。

@codechef - MXMN@ Maximum and Minimum的更多相关文章

  1. leetcode[164] Maximum Gap

    梅西刚梅开二度,我也记一题. 在一个没排序的数组里,找出排序后的相邻数字的最大差值. 要求用线性时间和空间. 如果用nlgn的话,直接排序然后判断就可以了.so easy class Solution ...

  2. [Swift]LeetCode152. 乘积最大子序列 | Maximum Product Subarray

    Given an integer array nums, find the contiguous subarray within an array (containing at least one n ...

  3. [LeetCode] 111. Minimum Depth of Binary Tree ☆(二叉树的最小深度)

    [Leetcode] Maximum and Minimum Depth of Binary Tree 二叉树的最小最大深度 (最小有3种解法) 描述 解析 递归深度优先搜索 当求最大深度时,我们只要 ...

  4. 【leetcode 桶排序】Maximum Gap

    1.题目 Given an unsorted array, find the maximum difference between the successive elements in its sor ...

  5. [LeetCode]152. Maximum Product Subarray

    This a task that asks u to compute the maximum product from a continue subarray. However, you need t ...

  6. LeetCode 164. Maximum Gap[翻译]

    164. Maximum Gap 164. 最大间隔 Given an unsorted array, find the maximum difference between the successi ...

  7. HTML5游戏源码 飞翔的字母 可自定义内容

    相信大家都玩过飞翔的小鸟吧,当然,可能已经有很多人因为这个游戏砸了不少手机.吼吼. 废话不多说,回到主题,源码如下. 博客园上传空间大小有限制,没法上传了,需要打包源码的朋友们请留言邮箱地址.当然还有 ...

  8. ASP.NET MVC5+EF6+EasyUI 后台管理系统(33)-MVC 表单验证

    系列目录 注:本节阅读需要有MVC 自定义验证的基础,否则比较吃力 一直以来表单的验证都是不可或缺的,微软的东西还是做得比较人性化的,从webform到MVC,都做到了双向验证 单单的用js实现的前端 ...

  9. XE2:查看Extended Events收集的数据

    SQL Server 使用Target来存储Events,Target 能够将Events存储到File中(扩展名是 xel),或 memoy buffer 中(Ring Buffer),Event ...

随机推荐

  1. 多机MySQL一主双从详细安装主从复制

    多机MySQL一主双从详细安装 一.复制的工作原理 要想实现AB复制,那么前提是master上必须要开启二进制日志 1.首先master将数据更新记录到二进制日志文件 2.从slave start开始 ...

  2. cmd下带参数执行python文件

    在一个文件下下创建程序代码,     sys.argv 即后续cmd中需要传入的参数列表,     sys.argv[0]即要执行的文件名     sys.argv[n]即参数的字符串 # -*- c ...

  3. solr源码解读(转)

    solr源码解读(转)原文地址:http://blog.csdn.net/duck_genuine/article/details/6962624 配置 solr 对一个搜索请求的的流程 在solrc ...

  4. 洛谷P1352 没有上司的舞会 [2017年5月计划 清北学堂51精英班Day3]

    P1352 没有上司的舞会 题目描述 某大学有N个职员,编号为1~N.他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子 结点的直接上司.现在有个周年庆宴会,宴会每邀请来一个职 ...

  5. 洛谷P1164 小A点菜 [2017年4月计划 动态规划08]

    P1164 小A点菜 题目背景 uim神犇拿到了uoi的ra(镭牌)后,立刻拉着基友小A到了一家……餐馆,很低端的那种. uim指着墙上的价目表(太低级了没有菜单),说:“随便点”. 题目描述 不过u ...

  6. PHP 学习1.2

    1. 流程控制 <html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv=& ...

  7. HR招聘_(九)_招聘方法论(面试环节·薪资谈判和心理把控)

    .薪资谈判 薪资谈判在整个过程中非常重要,如果这一环出现问题前期的所有付出都功亏一篑,无法达成招聘目标. 谈判过程中需要遵循以下原则: 明确 通过面试后需要再次确认候选人的目前薪资和期望,虽然第一次电 ...

  8. 从0开始学习 GitHub 系列之「03.Git 速成」

    前面的 GitHub 系列文章介绍过,GitHub 是基于 Git 的,所以也就意味着 Git 是基础,如果你不会 Git ,那么接下来你完全继续不下去,所以今天的教程就来说说 Git ,当然关于 G ...

  9. Thread.sleep

    Thread.sleep() The current thread changes state from Running to Waiting/Blocked as shown in the diag ...

  10. DIV+CSS网页布局常用的一些基础知识整理

    CSS命名规范 一.文件命名规范 全局样式:global.css: 框架布局:layout.css: 字体样式:font.css: 链接样式:link.css: 打印样式:print.css: 二.常 ...