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保险些。