@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. Codeforces Round #573 (Div. 2)

    A:Tokitsukaze and Enhancement 当时看错条件了..以为A>C>B>D.就胡写了判断条件. #include<bits/stdc++.h> us ...

  2. golang之vscode环境配置

    go语言开发,选择vscode作为IDE工具也是一个不错的选择,毕竟goland收费,老是破解也挺麻烦,除了这点,不过说实话挺好用的.vscode的话相对来说就毕竟原始,适合初学者. 1.vscode ...

  3. 内核、中断和网络 $ sysctl -a | grep ...$ cat /proc/interrupts$ cat /proc/net/ip_conntrack /* may take some time on busy servers */$ netstat$ ss -s

    你的中断请求是否是均衡地分配给CPU处理,还是会有某个CPU的核因为大量的网络中断请求或者RAID请求而过载了? SWAP交换的设置是什么?对于工作站来说swappinness 设为 60 就很好, ...

  4. python之pip

    sudo vim /usr/bin/lsb_release 确保第一行是python2.7,不然无法使用pip安装第三方依赖

  5. jmeter 之 https 请求

    本文为自己学习总结,转载需说明出处. jmeter发送https请求需要在巨jmeter中导入被测试网站的安全证书. 一.通过浏览器登录对应的网站导出安全证书.下面操作以360浏览器为例: 二.把导出 ...

  6. 图文结合深入理解 JS 中的 this 值

    图文结合深入理解 JS 中的 this 值 在 JS 中最常见的莫过于函数了,在函数(方法)中 this 的出现频率特别高,那么 this 到底是什么呢,今天就和大家一起学习总结一下 JS 中的 th ...

  7. C++学习笔记(2)---2.5 C++函数编译原理和成员函数的实现

    转载自:http://c.biancheng.NET/cpp/biancheng/view/2996.html点击打开链接 从上节的例子可以看出,对象的内存模型中只保留了成员变量,除此之外没有任何其他 ...

  8. python编写购物车小程序

     #练习#程序购物车#启动程序后,让用户输入工资,  然后打印商品列表,允许用户根据商品编号购买商品用户选择商品后 #检测余额是否够,够就直接扣款,不够就提醒可随时退出,退出时,打印已购买商品和余额  ...

  9. 【水滴石穿】FirstReactNativeProject

    这个是一个小demo,项目地址为https://github.com/prsioner/FirstReactNativeProject 有注册,忘记密码还有登陆,应该是用到了react-navigat ...

  10. input的表单验证(不断更新中~~)

    1 手机号验证 <input type="tel" id="phone" name="phone" placeholder=" ...