51nod2614 小b爱旅行

本文讲解了如何利用异或性质和线性基来求解图中路径的最小表示,通过添加和删除边进行优化,复杂度为O(60nlog2n)。核心在于将环的权值转化为线性基,并维护最小表示的更新。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题面

在这里插入图片描述
题目链接

解题思路

先不考虑删边,我们能够观察到如下重要的性质:
1.做出图的生成树,则任意一条路径可以由生成树的上路径异或上若干环组成。
2.一条路径可以并上任何一个环,即使不相邻。 因为我们可以直接走到那个环,走一圈然后原路返回。因为异或性质,中间路径被消除了。
我们可以将环的权值扔进线性基里,1到任何一点的距离去和线性基求答案。我们可以用线性基将一个距离削成最小表示。如果两个距离的最小表示相同,则他们通过线性基构成的东西也相同。
则答案是 距离的最小表示不同的个数 * 2^(线性基的大小)。
考虑删边,我们倒着做,就是加边。
我们将和1不相邻的连通块和环存起来,在连接时加入。
如果线性基改变,那么最小表示也将改变,因此我们要重构最小表示。由于线性基至多改变60次,复杂度是合法的。
复杂度 O ( 60 n l o g 2 n ) O(60nlog_2n) O(60nlog2n)

代码

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#include <set>
using namespace std;

#define MP make_pair

typedef long long ll;

template <class T>
void read(T &x) {
	x = 0; char c = getchar();
	while (c < '0' || c > '9') c = getchar();
	while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
}

void write(ll x) {
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}

const int N = 2e5 + 100;
const int M = 65;

ll p[M], sz;

bool insert(ll x) {
	for (int i = 60; i >= 0; i--) {
		if (x & (1LL << i)) {
			if (p[i]) x ^= p[i];
			else {
				p[i] = x;
				sz++;
				return true;
			}
		}
	}
	return false;
}

ll fun(ll x) {
	for (int i = 60; i >= 0; i--) 
		if ((x & (1LL << i)) && p[i]) x ^= p[i];
	return x;
}

int fa[N];

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

bool join(int x, int y) {
	x = find(x); y = find(y);
	if (x == y) return false;
	fa[x] = y;
	return true;
}

int n, m, q;
int uu[N], vv[N], id[N], has[N];
bool vis[N];
ll ww[N], dis[N], ans[N];
vector<pair<int, ll> > V[N];
vector<int> R[N];
set<ll> S;

void dfs(int u, int fa) {
	S.insert(fun(dis[u]));
	for (auto x : V[u]) {
		ll v = x.first, w = x.second;
		if (v == fa) continue;
		dis[v] = dis[u] ^ w;
		dfs(v, u);
	}
}

void build() {
	set<ll> s;
	for (auto it = S.begin(); it != S.end(); it++) s.insert(fun(*it));
	swap(s, S);
}

int main() {
	//freopen("0.txt", "r", stdin);
	read(n); read(m); read(q);
	for (int i = 1; i <= m; i++) read(uu[i]), read(vv[i]), read(ww[i]);
	for (int i = 1; i <= q; i++) read(id[i]), vis[id[i]] = true;
	for (int i = 1; i <= n; i++) fa[i] = i;
	for (int i = 1; i <= m; i++) {
		if (vis[i]) continue;
		if (join(uu[i], vv[i])) {
			V[uu[i]].push_back(MP(vv[i], ww[i]));
			V[vv[i]].push_back(MP(uu[i], ww[i]));
		}
	}
	S.insert(0);
	dfs(1, 0);
	for (int i = 1; i <= m; i++) {
		if (vis[i]) continue;
		if (find(uu[i]) == find(1)) insert(dis[uu[i]] ^ dis[vv[i]] ^ ww[i]);
		else R[find(uu[i])].push_back(i);
	}
	build();
	ans[q + 1] = (1LL << sz) * S.size();
	for (int i = q; i >= 1; i--) {
		ll u = uu[id[i]], v = vv[id[i]], w = ww[id[i]];
		if (find(u) == find(v)) {
			if (find(u) == find(1)) {
				if (insert(dis[u] ^ dis[v] ^ w)) build();
			}
			else R[find(u)].push_back(id[i]);
		}
		else {
			if (find(u) == find(1) || find(v) == find(1)) {
				if (find(v) == find(1)) swap(u, v);
				dis[v] = dis[u] ^ w;
				V[u].push_back(MP(v, w));
				dfs(v, u);
				bool flag = false;
				for (int i : R[find(v)]) {
					if (insert(dis[uu[i]] ^ dis[vv[i]] ^ ww[i])) flag = true;
				}
				if (flag) build();
				join(u, v);
			}
			else {
				V[u].push_back(MP(v, w));
				V[v].push_back(MP(u, w));
				u = find(u); v = find(v);
				if (R[u].size() > R[v].size()) swap(u, v);
				for (int x : R[u]) R[v].push_back(x);
				join(u, v);
			}
		}
		ans[i] = (1LL << sz) * S.size();
	}
	for (int i = 1; i <= q + 1; i++) write(ans[i]), puts("");
	return 0;
}
### 关于51Nod平台上编号为1020的问题详情与解答 #### 问题描述 在51Nod平台上的第1020号问题是关于计算两个大整数相加的结果[^1]。给定两个正整数A和B,长度不超过10^6位,要求编写程序来求解这两个数的和。 #### 输入格式说明 输入数据由多组测试案例组成;每组测试案例占两行,分别表示要相加的大整数A和B。对于每一组测试案例,应当单独输出一行结果,即A+B的值。 #### 解决方案概述 解决此问题的关键在于处理超大数据类型的运算,在大多数编程语言中内置的数据类型无法直接支持如此大规模数值的操作。因此,可以采用字符串的方式来存储这些大整数,并实现逐位相加逻辑,同时考虑进位情况。 下面是一个Python版本的具体实现方法: ```python def add_large_numbers(a: str, b: str) -> str: # Reverse strings to make addition easier from least significant digit a = a[::-1] b = b[::-1] carry = 0 result = [] max_length = max(len(a), len(b)) for i in range(max_length): digit_a = int(a[i]) if i < len(a) else 0 digit_b = int(b[i]) if i < len(b) else 0 total = digit_a + digit_b + carry carry = total // 10 current_digit = total % 10 result.append(str(current_digit)) if carry != 0: result.append(str(carry)) return ''.join(reversed(result)) if __name__ == "__main__": while True: try: num1 = input().strip() num2 = input().strip() print(add_large_numbers(num1, num2)) except EOFError: break ``` 该代码片段定义了一个函数`add_large_numbers`用于接收两个作为参数传入的大整数(形式上为字符串),并返回它们之和同样作为一个字符串。通过反转输入字符串使得最低有效位位于索引位置0处从而简化了按位累加的过程。最后再将得到的结果列表反向拼接成最终答案输出。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值