CF1322C Instant Noodles(思维,哈希)

CF1322C Instant Noodles(思维,哈希)

题意

给定一个大小为 \(2 * n\) 的二分图,该图右部点 \(i\) 有权值 \(c_i\) 。定义 \(S\) 表示非空的左部点集,\(f(S)\)\(S\) 中的点相连的所有右部点权和。

求对所有的 \(S\)\(f(S)\)\(gcd\)

思路

题意有点绕,先考虑暴力解。答案形式是若干点权和求 \(gcd\) 的形式,考虑到 \(gcd\) 的结合率,我们可以试着拆分答案 \(gcd\) 分开考虑。

大概长成这样: \(gcd(......) = gcd(gcd(..),gcd(..),gcd(..)...)\)

现在考虑这些小 \(gcd\) 是什么,考虑到右部点是权值的提供者分析其对答案贡献,任取 \(u\)\(v\),为右部中两点,这两点的贡献和左部点的连接情况有关。

我们设 \(T(i)\) 表示右部点 \(i\) 所连接的左部点集。

当 $T(u) = T(v) $ ,当 \(u \in S\) 时有, \(v \in S\) 。所有可以合并成一个点。

\(T(u) \cap T(v) \neq \emptyset\) ,就会有 $ { u }\(,\) { u ,v}\(,\) { u,v,u + v }$ 等情况。因为 \(S\) 取到了所有情况,最后对答案的贡献合并为 \(gcd(u,v,u + v)\) ,有个经典结论 $gcd(u,v) = gcd(u,v,u + v) $ 。该情况下 \(u,v\) 对答案的贡献就是 \(gcd(u,v)\)

\(T(u) \cap T(v) = \emptyset\) ,不可能出现 \(u + v\) 的情况。贡献仍然是 \(gcd(u,v)\)

综上,我们发现仅有第一种情况需要合并两点权,否则贡献仍然是对单点求 \(gcd\)

因此我们仅要做集合查重,再求这些集合的 \(gcd\) 即可。这个操作可以通过对集合hash完成。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<set>
#include<queue>
#include<map>
#include<string>
#include<random>
#include<iomanip>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define ull unsigned long long
#define endl '\n'
#define int long long
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
using namespace std;
mt19937 mrand(random_device{}());
int rnd(int x) { return mrand() % x;}
const int MAXN =10 + 2e5 ,mod=1e9 + 9;
// Hash
const int mod1 = 1e9 + 7;
const int mod2 = 1e9 + 9;
typedef pair<int,int> Hash;
Hash B,pw[MAXN];
Hash operator+ (Hash lv,Hash rv){
	int x = lv.first + rv.first, y = lv.second + rv.second;
	if(x >= mod1) x += mod1;
	if(y >= mod2) y += mod2;
	return make_pair(x,y);
}
Hash operator- (Hash lv,Hash rv){
	int x = lv.first - rv.first, y = lv.second - rv.second;
	if(x < 0) x += mod1;
	if(y < 0) y += mod2;
	return make_pair(x,y);
}
Hash operator* (Hash lv,Hash rv){
	return make_pair(lv.first * rv.first % mod1, lv.second * rv.second % mod2);
}
void hashInit() {
	B = Hash(rnd(mod1), rnd(mod2));
	pw[0] = Hash(1,1);
	for(int i = 1;i < MAXN;i ++) pw[i] = pw[i - 1] * B;
}
// --Hash-END--
void solve()
{    
	int n,m; cin >> n >> m;
	vector<int> c(n + 1);
	rep(i,1,n) cin >> c[i];
	vector<vector<int>> s(n + 1);
	rep(i,1,m) {
		int u,v; cin >> u >> v;
		// v : right  need to get S(v)
		s[v].push_back(u);
	}
	// set hash
	map<Hash,int> mp;
	rep(i,1,n) {
		if(s[i].empty()) continue;
		Hash hv = Hash(0,0);
		sort(s[i].begin(),s[i].end());
		for(auto x : s[i]) {
			hv = hv * B + Hash(x,x);
		}
		mp[hv] += c[i];
	}
	int ans = 0;
	for(auto [h,sc] : mp) ans = ans ? gcd(ans,sc) : sc;
	cout << ans << endl;
}
signed main()
{
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);

	hashInit();
	int T;cin>>T;
	while(T--)
		solve();

	return 0;
}

记得排序保证点集有序,另外单hash也可以过,双hash保险些。

posted @ 2022-06-21 18:36  Mxrurush  阅读(40)  评论(0编辑  收藏  举报