并查集+思维,CF 1039C - Network Safety

目录

一、题目

1、题目描述

2、输入输出

2.1输入

2.2输出

3、原题链接

二、解题报告

1、思路分析

2、复杂度

3、代码详解


一、题目

1、题目描述

2、输入输出

2.1输入

2.2输出

3、原题链接

1039C - Network Safety


二、解题报告

1、思路分析

考虑边<u, v>,如果 x = a[u] ^ a[v],那么u v 必须同时取/不取

那么对于所有的 a[u] ^ a[v] = x 的边构成的连通块,取x时只要有一个点被选整个连通块都得被选

m条边最多贡献 m 个 x

我们按值考虑,一共有2 ^ k个值,我们预处理出cnt个不同的x,那么对于 2 ^ k - x个值,整个点集对于他们而言就是随便选的,每个值贡献是2 ^ n

对于每个x涉及的连通块,假如连通块为sz,那么每个连通块选或不选,贡献就是2 ^ sz

具体实现时,我们可以用 哈希表/平衡树计数,并查集维护

对于每个x只重置涉及到的点在并查集中的值来保证这部分O(M)的总体复杂度

2、复杂度

时间复杂度: O(N + M)空间复杂度:O(N + M)

3、代码详解

 ​
#include <bits/stdc++.h>

using i64 = long long;
using i32 = unsigned int;
using u64 = unsigned long long;
using i128 = __int128;

constexpr int inf32 = 1E9 + 7;
constexpr i64 inf64 = 1E18 + 7;
constexpr int P = 1'000'000'007;

struct DSU {
    std::vector<int> p;
    int n, sz;
    DSU(int _n) {
    	init(_n);
    }
    
    void init (int _n) {
    	sz = n = _n;
        p.assign(n, -1);
    }

    int find(int x) {
    	int res = x;

    	while (p[res] >= 0)
    		res = p[res];

    	while (p[x] >= 0) {
    		int t = p[x];
    		p[x] = res;
    		x = t;
    	}

    	return res;
    }

    bool merge(int x, int y) {
        int px = find(x), py = find(y);
        if (px == py) return false;
        if (p[px] > p[py]) std::swap(px, py);
        p[px] += p[py], p[py] = px;
        -- sz;
        return true;
    }

    int size(int x) {
        return -p[find(x)];
    }

    int size() const {
        return sz;
    }
};

struct custom_hash{
	static u64 splitmix64(u64 x) {
		x ^= x << 13;
		x ^= x >> 7;
		x ^= x << 17;
		return x;
	}

	u64 operator()(u64 x) const {
		static const u64 FIXED_RANDOM = std::chrono::steady_clock::now().time_since_epoch().count();
		return splitmix64(x + FIXED_RANDOM);
	}
};

void solve() {
	int n, m, k;
	std::cin >> n >> m >> k;

	std::unordered_map<u64, std::vector<int>, custom_hash> mp;

	std::vector<i64> a(n);

	for (int i = 0; i < n; ++ i) std::cin >> a[i];

	std::vector<std::pair<int, int>> e(m);

	for (int i = 0, u, v; i < m; ++ i) {
		std::cin >> u >> v;
		-- u, -- v;
		mp[a[u] ^ a[v]].push_back(i);
		e[i] = { u, v };
	}

	std::vector<int> pow2(std::max(n, k) + 1);
	pow2[0] = 1;
	for (int i = 1; i < pow2.size(); ++ i)
		pow2[i] = pow2[i - 1] * 2LL % P;

	int ans = (pow2[k] - (i64)mp.size()) % P * pow2[n] % P; 

	DSU dsu(n);

	for (auto &[x, g] : mp) {
		for (int i : g) {
			auto [u, v] = e[i];
			dsu.merge(u, v);
		}

		ans = (1LL * ans + pow2[dsu.size()]) % P;

		for (int i : g) {
			auto [u, v] = e[i];
			dsu.p[u] = -1;
			dsu.p[v] = -1;
		}

		dsu.sz = n;
	}

	std::cout << ans;
}

auto FIO = []{
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	std::cout.tie(nullptr);
	return 0;
}();

int main () {
	#ifdef DEBUG
		freopen("in.txt", "r", stdin);
		freopen("out.txt", "w", stdout);
	#endif
	
	int T = 1;
	// std::cin >> T;
	while (T --)
		solve();

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

EQUINOX1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值