@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. js控制“鼠标点击按钮后,按钮消失“(可以自己添加video标签控制播放)

  2. fastjson string[]转 json字符串

    @RequestMapping(value = "/friendlink_list/province_list", produces = {"application/js ...

  3. 大半夜吃饱了撑的帮人调IE玩

      那高手的也是IE6,我也是IE6,但是他的IE6就总是进recv,我的IE6就进WSARecv,一点都不科学...擦..不调了.

  4. 2019.9.24 csp-s模拟测试51(a) 反思总结

    T1:还在头铁,顺便复习了一下lct[虽然这题用不上因为复杂度不对] 头铁结束. 虽然题目存在换根的操作,实际上并不用真的换根. 2操作中求lca的时候只要考虑原树上root和x.y的lca以及x,y ...

  5. checkbox的全选,取消全选,获得选中值

    <html> <head> <title>jq全选以及获得选中项的值</title> <meta charset="utf-8" ...

  6. LintCode刷题笔记-- BackpackII

    标记: 动态规划 问题描述: Given n items with size Ai, an integer m denotes the size of a backpack. How full you ...

  7. 手机端点击键盘无法获取keyCode值的部分时隐藏键盘并执行事件

    用计时器监视window.innerHeight高度改变来判断.触发键盘其他地方也有事件反应 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 ...

  8. golang内置函数

  9. log4j日志系统

    在项目开发中,记录错误日志是一个很有必要功能.一是方便调试:二是便于发现系统运行过程中的错误:三是存储业务数据,便于后期分析: 在java中,记录日志,有很多种方式. 比如,自己实现. 自己写类,将日 ...

  10. ThinkPHP中_after_update、_before_update等的用法

    https://blog.csdn.net/aslackers/article/details/50339163 TP系统\Think\Model类里隐藏了几个有用的方法: _before_inser ...