『题解』Luogu P8396 [CCC 2022 S2] Good Groups
题目大意
一个班级被分成 \(g\) 个组,每组三个人,现有一些约束:
- 某对学生必须在同一小组。
- 某对学生必须不在同一小组。
现给出分组方案,以及约束条件若干条,求有多少条约束不成立。
思路
双倍经验。
既然涉及到分组,那么我的第一反应就是并查集。
使用并查集记录学生的分组情况,然后对所有约束条件一一判断,若有不成立的条件,就计入答案。
但还有一个问题:学生的名字为字符串,不是数字。
于是就需要将这些字符串转化为数字。
考虑用 map
记录所有名字的下标,如果第一次遇到某个名字 \(s\),m[s]
一定为 \(0\),这种情况给 \(s\) 分配下一个未使用的下标即可。注意下标不能使用 \(0\),否则每次都会重新分配下标。
作为一个非不吸氧 A 题不可的人,我决定再考虑一下字符串哈希。
可是很多种 hash 方法都会 WA 几个点。
于是将并查集范围开到 \(10^7\),试图减少 hash 冲突...
还是一片红......
最终得到,使用 BKDR hash 可过此题。
上面那篇文章中说到,need
取 \(2^n-1\) 比较好,那么我们就取 \(2^6-1\) 吧,也就是 \(63\)。
代码是这样的:
int hs(string s){
unsigned int res=0;
for(int i=0; i<s.size(); i++)
res=(res*63)^s[i];
return res%(int)1e7; // 最后需要mod1e7,
}
再交一发,这题就没了。
代码
#include <iostream>
using namespace std;
const int N=1e5+5;
int x,y,g,ans;
string n1[N][2],n2[N][2],a,b,c;
template<class T=int>
class set{
public:
set(int n=1e5){f=new T[l=n+5]; clear();}
~set(){delete[] f;}
T find(T x){return f[x]==x ? x : f[x]=find(f[x]);}
void merge(T x,T y){f[find(x)]=find(y);}
void clear(){for(int i=0; i<l; i++) f[i]=i;}
private:
T *f;
int l;
};
set<int> s(1e7); // 并查集(构造函数传范围)
// 字符串哈希
int hs(string s){
unsigned int res=0;
for(int i=0; i<s.size(); i++)
res=res*63+s[i]; // BKDR 哈希
return res%(int)1e7;
// res需要作为数组下标,为了防止哈希冲突,将数组开到了 1e7
}
int main(){
scanf("%d",&x); // 约束 1 个数
for(int i=1; i<=x; i++) cin >> n1[i][0] >> n1[i][1];
scanf("%d",&y); // 约束 2 个数
for(int i=1; i<=y; i++) cin >> n2[i][0] >> n2[i][1];
scanf("%d",&g); // 组数
for(int i=1; i<=g; i++){
cin >> a >> b >> c; // a,b,c 在同一组
s.merge(hs(a),hs(b)); // 将其合并
s.merge(hs(a),hs(c));
}
for(int i=1; i<=x; i++) // 判断是否违反约束 1
if(s.find(hs(n1[i][0]))!=s.find(hs(n1[i][1]))) ans++;
for(int i=1; i<=y; i++) // 同上,约束 2
if(s.find(hs(n2[i][0]))==s.find(hs(n2[i][1]))) ans++;
printf("%d\n",ans);
return 0;
}