【杭电多校训练 2018】Always Online(仙人掌图 + 最小割)

题目

【杭电多校训练 2018】Always Online

给定一个 n n n 个点 m m m 条边的无向带权联通图,没有重边、自环,且每两个点之间都有至多两条路径,它们的边集不相交。求:


∑ 1 ≤ s &lt; e ≤ n s ⊕ e ⊕ m a x F l o w ( s , e ) \sum_{1 \le s \lt e \le n} s \oplus e \oplus maxFlow(s, e) 1s<ensemaxFlow(s,e)


其中 ⊕ \oplus 表示按位异或。 m a x F l o w ( s , e ) maxFlow(s, e) maxFlow(s,e) 表示 s s s 点到 e e e 点的最大流。

T T T 组数据, T ≤ 100 , n ≤ 1 0 5 , ∑ n ≤ 1 0 6 , w i ≤ 1 0 9 T \le 100, n \le 10^5, \sum n \le 10^6, w_i \le 10^9 T100,n105,n106,wi109

分析

仙人掌图

定义

仙人掌是不包含自环的,一条边最多属于一个简单环的无向连通图。

1 1 1 是仙人掌:
图 1
2 2 2 不是仙人掌,因为边 ( B , G ) (\text{B}, \text{G}) (B,G) 属于两个简单环:
图 2
特别地,树也是仙人掌:
图 3
另外,每个联通块都是仙人掌的无向图被称为沙漠。

性质

  • 结点数为 n n n 的仙人掌边数最少为 n − 1 n - 1 n1(树),最多为 3 2 ( n − 1 ) \frac{3}{2}(n - 1) 23(n1)。读者可以自行构造。
  • 仙人掌的任意两个点之间都只有有至多两条路径,它们的边集不相交。你可以把仙人掌看作是一堆环和一堆散边组合起来,使得两个环最多只有一个公共点。我们称这些散边为树边。
  • 仙人掌两个点的最短路为路径上的每条树边的边权与每个环左半边和右半边的边权和的最小值的和。类似地,仙人掌两个点的最小割为路径上的每条树边的权值与每个环左半边的最小权值加上它右半边的最小权值的和的最小值。

本题分析

问题转化

根据 最大流 - 最小割定理 \text{最大流 - 最小割定理} 最大流 - 最小割定理,题目等价于求:


∑ 1 ≤ s &lt; e ≤ n s ⊕ e ⊕ m i n C u t ( s , e ) \sum_{1 \le s \lt e \le n} s \oplus e \oplus minCut(s, e) 1s<enseminCut(s,e)


其中 m i n C u t ( s , e ) minCut(s, e) minCut(s,e) 表示 s s s 点到 e e e 点的最小割。

对于仙人掌上的最小割在环上的情况,我们考虑如何把它转化为在链上的情况。发现如果把一个环切成两半,那么两半中的边权最小值中至少有一个等于整个环上的边权最小值。那么,我们把整个环上的边权最小的那条边去掉,并把其他边的边权加上这个最小值。这样,环就变成了一个链,两个点之间的最小割就转化成了链上的最小值,于是我们就可以把它和树边的情况一样考虑。

树上问题

我们已经把仙人掌变成了树,现在要计算的就是:


∑ 1 ≤ s &lt; e ≤ n s ⊕ e ⊕ m i n V a l u e ( s , e ) \sum_{1 \le s \lt e \le n} s \oplus e \oplus minValue(s, e) 1s<enseminValue(s,e)


其中 m i n V a l u e ( s , e ) minValue(s, e) minValue(s,e) 表示 s s s 点到 e e e 点路径上的最小边权。

现将所有边储存起来,从大到小排序,然后对于每条边,合并两个端点所在的联通块,并在同时计算贡献即可。由于异或的性质,我们只需对于每一位分别考虑,对于每个联通块维护它包含的所有点的编号中的每一个 bit \text{bit} bit 有几个 0 0 0,几个 1 1 1 就可以了。时间复杂度 Θ ( n log ⁡ 2 2 n ) \Theta(n \log_2^2 n) Θ(nlog22n)

实现

找环

如何找到仙人掌上的所有树边和环呢?我们先跑一遍 tarjan \text{tarjan} tarjan 求出原图中的点双联通分量,于是对于每条树边,它一定是图的一个桥。对于每个环的最高点 u u u,一定存在一个相邻的点 v v v,使得 dfn [ v ] &gt; dfn [ u ] \text{dfn}[v] &gt; \text{dfn}[u] dfn[v]>dfn[u],并且 u u u 没有直接 dfs \text{dfs} dfs v v v

细节

注意本题的最大答案为 1 0 10 2 × 2 ⋅ 1 0 9 = 1 0 19 \frac{10^{10}}{2} \times 2 \cdot 10^9 = 10^{19} 21010×2109=1019,而 long long 类型能够储存的最大整数只有大约 9.2233 × 1 0 18 9.2233 \times 10^{18} 9.2233×1018,所以我们应该使用 unsigned long long 类型储存答案。代码采用启发式合并的并查集来维护每个联通块的信息,静态仙人掌图问题的通用处理方法包含在代码中。

代码

#include <cstdio>
#include <algorithm>
using namespace std;

typedef unsigned long long ull;
const int maxn = 1e5 + 5, maxm = 3e5 + 5, logn = 30 + 5;
int T, n, m, fa[maxn], sz[maxn], cnt[maxn][logn][2];
int tot, ter[maxm], wei[maxm], nxt[maxm], lnk[maxn];
int cur, dfn[maxn], low[maxn], par[maxn], ret[maxn], temp[maxn];

struct edge {
	int u, v, w;

	edge(int u = 0, int v = 0, int w = 0): u(u), v(v), w(w) {}

	friend bool operator<(edge a, edge b) {
		return a.w > b.w;
	}
} a[maxn];

void addEdge(int u, int v, int w) {
	nxt[++tot] = lnk[u];
	lnk[u] = tot;
	ter[tot] = v;
	wei[tot] = w;
}

int another(int e) {
	return ((e - 1) ^ 1) + 1;
}

void circle(int u, int v, int i) {
	int cnt = 0, x = v;
	temp[++cnt] = i;
	while (x != u) {
		temp[++cnt] = ret[x];
		x = par[x];
	}
	int id = 1, mn = wei[temp[1]];
	for (int i = 1; i <= cnt; i++) {
		if (wei[temp[i]] < mn) {
			mn = wei[temp[i]];
			id = i;
		}
	}
	for (int i = 1; i <= cnt; i++) {
		if (i != id) {
			a[++m] = edge(ter[temp[i]], ter[another(temp[i])], wei[temp[i]] + wei[temp[id]]);
		}
	}
}

void tarjan(int u, int p) {
	dfn[u] = low[u] = ++cur;
	for (int i = lnk[u], v; i; i = nxt[i]) {
		v = ter[i];
		if (v == p) continue;
		if (dfn[v] == 0) {
			par[v] = u;
			ret[v] = i;
			tarjan(v, u);
			low[u] = min(low[u], low[v]);
		} else {
			low[u] = min(low[u], dfn[v]);
		}
	}
	for (int i = lnk[u], v, w; i; i = nxt[i]) {
		v = ter[i], w = wei[i];
		if (low[v] > dfn[u]) {
			a[++m] = edge(u, v, w);
		} else if (par[v] != u && dfn[v] > dfn[u]) {
			circle(u, v, i);
		}
	}
}

int find(int x) {
	return fa[x] == x ? x : find(fa[x]);
}

ull calc(int x, int y, int w) {
	ull res = 0;
	for (int i = 0; i <= 30; i++) {
		int b = ~w >> i & 1;
		for (int j = 0; j < 2; j++) {
			res += (1ull << i) * cnt[x][i][j] * cnt[y][i][j ^ b];
		}
	}
	return res;
}

void merge(int x, int y) {
	if (sz[x] < sz[y]) swap(x, y);
	for (int i = 0; i <= 30; i++) {
		for (int j = 0; j < 2; j++) {
			cnt[x][i][j] += cnt[y][i][j];
			cnt[y][i][j] = 0;
		}
	}
	sz[x] += sz[y], sz[y] = 0;
	fa[y] = x;
}

int main() {
	scanf("%d", &T);
	while (T--) {
		scanf("%d %d", &n, &m);
		tot = 0;
		for (int i = 1; i <= n; i++) {
			lnk[i] = 0;
		}
		for (int i = 1, u, v, w; i <= m; i++) {
			scanf("%d %d %d", &u, &v, &w);
			addEdge(u, v, w), addEdge(v, u, w);
		}

		m = cur = 0;
		for (int i = 1; i <= n; i++) {
			dfn[i] = low[i] = par[i] = ret[i] = 0;
		}
		tarjan(1, 0);

		sort(a + 1, a + m + 1);
		for (int i = 1; i <= n; i++) {
			fa[i] = i, sz[i] = 1;
			for (int j = 0; j <= 30; j++) {
				cnt[i][j][i >> j & 1] = 1;
				cnt[i][j][~i >> j & 1] = 0;
			}
		}
		ull ans = 0;
		for (int i = 1; i <= m; i++) {
			int x = find(a[i].u), y = find(a[i].v);
			ans += calc(x, y, a[i].w);
			merge(x, y);
		}
		printf("%llu\n", ans);
	}
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
SQL Server Always On是一种高可用性和灾难恢复解决方案,它基于SQL Server数据库引擎和Windows Server故障转移集群(Failover Cluster)技术。其架构如下: 1. 客户端:客户端通过应用程序或者数据库工具与主服务器进行交互,如查询、插入、更新等操作。 2. 链接服务/负载均衡:链接服务是一个可选的组件,它负责将客户端连接导向到可用的数据库节点。负载均衡器会根据负载情况将客户端请求分配到相应的数据库节点。 3. 主服务器(Primary):主服务器是Always On配置中的核心组成部分,它承担主要的读写操作。主服务器上的写操作会同步到其他辅助服务器,确保数据的一致性。 4. 辅助服务器(Secondary):辅助服务器是备份主服务器的复本,用于故障转移和读取操作。辅助服务器可以是同城或异地,以实现容灾备份和数据复制。 5. 同步复制(Synchronous replication):主服务器上的写操作会同步地传输到辅助服务器,确保数据的一致性。只有当辅助服务器确认接收到数据后,主服务器才会返回操作完成。 6. 异步复制(Asynchronous replication):主服务器上的写操作异步地传输到辅助服务器。主服务器无需等待辅助服务器确认,能够提供更高的性能,但数据同步的延迟较高。 7. 可读辅助服务器(Readable Secondary):辅助服务器可以配置为只读模式,允许用户进行读取操作。这可以提高性能和负载均衡。 8. 监听器(Listener):监听器是客户端连接到Always On的入口,它会负责向客户端提供主服务器或可读的辅助服务器的连接端点。 Always On架构提供了高可用性、灾难恢复和性能改进。当主服务器发生故障时,自动进行故障转移,辅助服务器会接管主服务器的工作。这样可以最小化应用程序和用户的中断时间,确保业务的连续性和数据的安全性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值