@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. LintCode 两两交换链表中的节点

    给一个链表,两两交换其中的节点,然后返回交换后的链表. 样例 给出 1->2->3->4, 你应该返回的链表是 2->1->4->3. 分析:第一次调试的时候用了P ...

  2. https方式nginx 代理tomcat访问不带www的域名301重定向跳转到www的域名帮助seo集中权重

    比如我要把 http://gucanhui.com http://www.gucanhui.com 跳转到https://www.gucanhui.com 用F12的network可以看到状态码301 ...

  3. netbeans生成的maven工程没有web.xml文件 如何新建

    使用netbeans生成的maven工程没有web.xml 需要自己新建 步骤: 下一步,完成

  4. 解决github下载慢的终极方法

    直接用ssr代理,使用全局代理. 下载墙外的软件,都可以,比如 GithubDsktop

  5. JAVA短信验证码 工具类

    MsgCodeUtil.java package com.hg.util; import com.soyea.enums.ResultEnum; import com.soyea.exception. ...

  6. 2019.7.27 NOIP模拟测试9 反思总结

    先来整理题目 T1题目大意:给出n个数字和一个质数作为模数,一个变量x初始值为1.进行m次操作,每次让x随机乘上n个数中的一个,问m次操作以后x的期望值. 答案一定可以用分数表示,输出分子乘分母逆元的 ...

  7. Redhat/Fedora 或类似系统, 配置网络的工具介绍

    在Redhat早期的版本中, 有linuxconf .redhat-config-network .netconfig 等工具: 在Redhat/Fedora 最新的版本有 system-config ...

  8. webpack学习之——模块(Modules)

    在模块化编程中,开发者将程序分解成离散功能块(discrete chunks of functionality),并称之为模块. 每个模块具有比完整程序更小的接触面,使得校验.调试.测试轻而易举. 精 ...

  9. JS---案例:手风琴 (利用封装好的动画函数)

    案例:手风琴     封装好的动画函数在common.js里面     //function getStyle(element, attr) {...}     //function animate( ...

  10. Can you find it? HDU-2141 (二分查找模版题)

    Description Give you three sequences of numbers A, B, C, then we give you a number X. Now you need t ...