• Solution 「APIO 2018」「洛谷 P4630」铁人两项


    \(\mathcal{Description}\)

      Link.

      给定一个 \(n\) 个点 \(m\) 条边的无向图(不保证联通),求有序三元点对 \((s,c,f)\) 的个数,满足 \(s,c,f\) 互不相同,且存在一条从 \(s\)\(c\) 再到 \(f\) 的简单路径。

      \(n\le10^5\)\(m\le2\times10^5\)

    \(\mathcal{Solution}\)

      首先考虑这样一个问题,若 \(s,c,f\) 在同一点双中,是否一定满足条件。

      答案是肯定的,这里介绍一种 CF 某题解上提到的证明。

    性质证明

      构造网络,将点双中的每个点拆点。对于点 \(u\),连接 \((u_i,u_o,1)\)。对于原图中的边 \((u,v)\),连接 \((u_o,v_i,1)\)。对于选定的 \(s,c,f\),连接 \((S,c_i,2),(s_o,T,1),(f_o,T,1)\),接下来只需要证明该网络的最大流为 \(2\)

      那么只需要考虑最小割 \(C\)。显然 \(C\le2\),则只需证 \(C>1\)

      首先,割掉 \((S,c_i,2)\) 或同时割掉 \((s_o,T,1),(f_o,T,1)\) 都不能使 \(C\le1\)。接下来考虑其它类型的边。

    • 割掉 \((u_i,u_o,1)\),相当于删除 \(u\) 点。因为这是一个点双,所以 \(c\)\(s,f\) 仍然连通,不满足。

    • 割掉 \((u_o,v_i,1)\),相当于删除 \((u,v)\) 边。显然其对于连通性的影响不大于删除 \(u\) 点或 \(v\) 点,由上种情况,亦不满足。

      到此,有 \(C>1\)。由因为 \(C\le2\)\(C\in\mathbb N\),所以 \(C=2\)。那么这样的路径一定存在,证毕。

      那么如果固定 \(s,f\),合法的 \(c\) 就可以在圆方树 \(s\)\(f\) 路径上的所有圆点和所有方点所代表的圆点(除去 \(s,f\))。这是因为 \(c\) 取在任意点双内部,由我们的结论,都一定可以从某点进入点双,经过 \(c\),再从某点走出点双。

      但简单的计算会导致重复——一个圆点对多个方点有贡献。

      举个例子,对于 \(u-w-v\)\(w\) 是圆点,\(u,v\) 是方点,如果我们单纯地用点双大小作为方点的权值,\(w\) 就会在 \(u\)\(v\) 中分别计算一次。

      解决办法很巧妙:将圆点的权值设为 \(-1\)。考虑 \(s\)\(f\) 的路径必然是”圆-方-圆-……-圆-方-圆“,两个端点的 \(-1\),去除了 \(s\)\(f\) 的贡献,中间的 \(-1\) 去除了在左右的”方“中重复的贡献。那么,合法的 \(c\) 的数量就是 \(s\)\(f\) 的树上路径权值之和。

      于是,相当于求树上点对的路径权值和。反过来,固定 \(c\),维护子树信息求出 \((s,f)\) 的方案数,计算 \(c\) 的贡献即可。

      复杂度 \(\mathcal O(n)\)

    \(\mathcal{Code}\)

    #include <cstdio>
    
    const int MAXN = 1e5, MAXM = 2e5;
    int n, m, q, snode;
    int dfc, top, dfn[MAXN + 5], low[MAXN + 5], stk[MAXN + 5];
    int siz[MAXN * 2 + 5], val[MAXN * 2 + 5];
    long long ans;
    
    struct Graph {
    	int ecnt, head[MAXN * 2 + 5], to[MAXM * 2 + 5], nxt[MAXM * 2 + 5];
    	inline void link ( const int s, const int t ) {
    		to[++ ecnt] = t, nxt[ecnt] = head[s];
    		head[s] = ecnt;
    	}
    	inline void add ( const int u, const int v ) {
    		link ( u, v ), link ( v, u );
    	}
    } src, tre;
    
    inline bool chkmin ( int& a, const int b ) { return b < a ? a = b, true : false; }
    
    inline void Tarjan ( const int u, const int f ) {
    	dfn[u] = low[u] = ++ dfc, val[stk[++ top] = u] = -1;
    	for ( int i = src.head[u], v; i; i = src.nxt[i] ) {
    		if ( ( v = src.to[i] ) == f ) continue;
    		if ( ! dfn[v] ) {
    			Tarjan ( v, u ), chkmin ( low[u], low[v] );
    			if ( low[v] >= dfn[u] ) {
    				tre.add ( u, ++ snode ), val[snode] = 1;
    				do tre.add ( snode, stk[top] ), ++ val[snode]; while ( stk[top --] ^ v );
    			}
    		} else chkmin ( low[u], dfn[v] );
    	}
    }
    
    inline void calc ( const int u, const int f ) {
    	siz[u] = u <= n;
    	for ( int i = tre.head[u], v; i; i = tre.nxt[i] ) {
    		if ( ( v = tre.to[i] ) ^ f ) {
    			calc ( v, u );
    			ans += 1ll * val[u] * siz[u] * siz[v];
    			siz[u] += siz[v];
    		}
    	}
    	ans += 1ll * val[u] * siz[u] * ( dfc - siz[u] );
    }
    
    int main () {
    	scanf ( "%d %d", &n, &m ), snode = n;
    	for ( int i = 1, u, v; i <= m; ++ i ) {
    		scanf ( "%d %d", &u, &v );
    		src.add ( u, v );
    	}
    	for ( int i = 1; i <= n; ++ i ) {
    		if ( ! dfn[i] ) {
    			dfc = top = 0;
    			Tarjan ( i, 0 );
    			calc ( i, 0 );
    		}
    	}
    	printf ( "%lld\n", ans << 1 );
    	return 0;
    }
    
  • 相关阅读:
    js函数柯理化
    Promise异步编程解决方案
    set和map结构,class类
    原创:用node.js搭建本地服务模拟接口访问实现数据模拟
    原创:微信小程序如何使用自定义组件
    原创:微信小程序开发要点总结
    Nodejs CMS——基于 NestJS/NuxtJS 的完整开源项目
    浅谈js对象之数据属性、访问器属性、Object.defineProperty方法
    Promise初步详解(resolve,reject,catch)
    原生js面向对象实现简单轮播
  • 原文地址:https://www.cnblogs.com/rainybunny/p/13363155.html
Copyright © 2020-2023  润新知