题意:你有n双袜子,要穿m天,每个袜子都有一种颜色,你有k种颜料,每天给你指定两双袜子,你每天要穿这两双袜子出门,但是这两双袜子必须颜色一样,你可以对任意一个袜子进行染色,这样他们就一样了,问你:穿下这m天,最少要涂多少种颜色。
思路:你m天穿的袜子,如果有某几天穿的袜子是一样的,那么这几天的袜子都必须一样。这就是并查集了。。。用并查集把那一部分所有的袜子都变成一种颜色,最少的涂法就是用这个集合所有的袜子减去颜色最多的袜子就是最少的涂法,这样的集合可能在这m天有好几个,就分别统计每个集合的涂法。
这种思路可以看出来很宏观,我拿到这题可能会想如果某天两个袜子颜色一样,要怎样,不一样要怎样,可能会分的很细。。但是看到这题感觉思路一下子被拉大了。。。
比如有n个数字,要你把这n个数字都变成一个数字,最少的变法,肯定是用一共的减去最多的数字啊。。。这个也一样。。分析知如果有某几天袜子一样,那这几天的袜子全都一样。。。总之知道这题思路之后感觉思维被拉大了。。。而且让某几天一样用并查集写。。我可能也想不到的。。而且这题的代码不好写。。。(我很弱,刚入acm不久,各位大佬见笑了。。
代码:(我会把我自己写过程中卡住的地方解释下。。。弱菜啊。。。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <map>
using namespace std;
const int maxn = 2e5 + 5;
int pre[maxn], a[maxn], b[maxn]; //pre并查集祖先数组,a记录每个袜子的颜色,b数组用来记录每个集合的颜色(就是某几天都是一种颜色,记录这种颜色的编号
vector<int> v[maxn];
int Find(int x)
{
return x == pre[x] ? x : pre[x] = Find(pre[x]);
}
void join(int x, int y)
{
pre[Find(y)] = Find(x);
}
int main()
{
int n, m, k, x, y;
scanf("%d%d%d", &n, &m, &k);
for(int i = 1; i <= n; i++)
scanf("%d", &a[i]), pre[i] = i;
for(int i = 1; i <= m; i++)
scanf("%d%d", &x, &y), join(x, y); //这里就是这一天要袜子颜色要一样,把他们“联通”起来,这里并查集“祖先”的个数就是一共有几种颜色
int cnt = 1;
for(int i = 1; i <= n; i++)
{
if(pre[i] == i) //这里如果pre[i] == i,代表了这是某几天的“祖先”,也就是一种颜色,给这个祖先的颜色一个编号cnt
b[i] = cnt++;
}
for(int i = 1; i <= n; i++) //这里就是把祖先的小弟拉近祖先的怀里,b[]是祖先的编号,他的小弟的b[Find()]的颜色(a记录的)都存进v数组里,v下标代表哪个
{<span style="white-space:pre"> </span>//祖先
v[b[Find(i)]].push_back(a[i]);
}
int ans = 0;
for(int i = 1; i < cnt; i++) //第一个for遍历cnt个祖先,也就是一共有cnt种不同的颜色
{
int maxx = 0;
map<int, int> mm;
for(int j = 0; j < v[i].size(); j++) //这里就是遍历刚才每种颜色收的小弟,用一个map记录每种颜色的数量,找出数量最多的颜色
{
mm[v[i][j]]++;
if(mm[v[i][j]] > maxx) maxx = mm[v[i][j]];
}
ans += v[i].size() - maxx; //这里就是用集合所有的袜子,减去数量最多的颜色。。。
}
printf("%d\n", ans);
return 0;
}