http://codeforces.com/problemset/problem/731/C

这个题的题意是。。小明的妈妈给小明留下了n只袜子,给你一个大小为n的颜色序列c

代表第i只袜子的颜色,小明的妈妈在以后的m天要求小明每天穿编号为l[i],r[i]所组成的一双袜子

小明觉得如果颜色不一样的话很丢人。。想一次把袜子的颜色全部改好。。使得。。既按照妈妈的指令去做

又能使每天穿的一双袜子颜色相同,问,最少改变几只袜子的颜色(能满足条件)

因为这个题是水题嘛,所以说我考虑了三种实现的方式

之前没有认真分析题的时候我去线性地遍历这个序列。。然后去乱搞。。瞎猜。。反证都没有

线性遍历是无法保证题目条件的

事实证明这样的做法是最费力不讨好的

那么如果我们深入这个题的话。。会观察得到一个性质。。小明每天穿的两只袜子颜色必然相同

那么这就相当于建立了一种联系,即给题目中的变量建立了联系,于是我们如何来维护这个联系呢

第一我们可以把他们都放到一个集合里面。。(我们找集合里面出现次数最多的颜色就好了)这个得实现方法,标记每个点的所属集合。。。vis数组

还可以用并查集(不过这个题没有查询操作不必强行并查集),还可以直接连边(这样连边连在一起的一颗森林,他们的颜色必定相同)

你用并查集可以非常容易地把那些点放到一个集合里去,当我们放进去之后。。问题来了。。

我一开始想的是。。如果你使用了并查集的路径压缩,那么你可以在很短的时间内找到一个点的所属集合的标号

我们只要扫一遍所有的元素就能遍历所有的集合。。但是如何分开统计呢。。我想到的是cnt[maxn][maxn]

第一维集合编号。。第二维。。记录每种颜色的个数。。集合编号最多2e5,颜色最多2e5,4e10的空间。。你怕不怕

但是这都是不经过仔细思考的结果。。然后我因为空间上这点事放弃了通过点找集合的遍历方法

但是如果你要通过集合编号要找所属的元素的话。。首先你不知道有哪些集合编号。。其次你也不知道集合里放了哪些元素

经典的并查集结构只能判定点的集合编号。。通过集合很难找到点。。需要枚举。。

第一枚举集合,第二枚举元素,2e5的两层循环,加了vis访问数组都超时了。。(即遍历过的元素不再跑)

而且如果你仍然用cnt[颜色值]++来统计的话。。每次遍历完一个集合。。你就要清空。。(这里的清空是重新赋值,应当与STL的clear不同)

那么这样你是铁定n^2复杂度了,T的你连妈妈都不认识哦

那么接下来我们怎么走呢?我们是一定需要记得之前的矛盾的。。

矛盾1,cnt[maxn][maxn]开不下。。首先有一点我们要清楚。。第一维是一定要开满的。。不遍历完你永远不知道

有几个集合,那第二维统计颜色,颜色值的数量与集合数量并不能同时取到最大。。具体来说。。如果你第二维开满

很多时候很多的颜色值将是空的。。这就造成了空间上的浪费。。因为第二维最多一共需要maxn个位置,而不是maxn*maxn

所以我们想到了动态开点释放内存。。这个还是不那么好写的。。我们可以用map<int,int>数组啊!!,然后统计完遍历map数组统计答案

这样我们就能通过点找集合的方式快速统计。。下面贴上代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>
using namespace std;
const int maxn=2e5+;
int n,m,k;
int c[maxn];
int l,r;
int fa[maxn];
bool vis[maxn];
map<int,int> mp[maxn];
void init(){
int i;
for(i=;i<=n;++i){
fa[i]=i;
}
}
int getFa(int v){
if(v==fa[v]) return v;
return fa[v]=getFa(fa[v]);
}
void Mix(int a,int b){
int af=getFa(a);
int bf=getFa(b);
if(af!=bf){
fa[bf]=af;
}
}
int main(){
scanf("%d%d%d",&n,&m,&k);
int i,j;
for(i=;i<=n;++i){
scanf("%d",&c[i]);
}
init();
for(i=;i<=m;++i){
scanf("%d%d",&l,&r);
Mix(l,r);
}
int sum=;int all,mx;
for(i=;i<=n;++i){
int father=getFa(i);
mp[father][c[i]]++;
}
for(i=;i<=n;++i){
int cnt=,mx=;
for(map<int,int>::iterator it=mp[i].begin();it!=mp[i].end();++it){
if(it->second>mx) mx=it->second;
cnt+=it->second;
}
sum+=cnt-mx;
}
printf("%d\n",sum);
return ;
}

第二种方法我们仍然使用并查集。。我们还可以采用集合遍历元素的方法来遍历并查集森林

但是我们并查集的原始结构可是不支持的。。你要是按这样的映射方式去统计的话。。仍然是要

第一维枚举集合编号。。第二维枚举所有元素。。两重循环爆炸(某些情况两重循环可并不一定是n^2的复杂度哦)

然后这时候其实一个通常的普遍的想法不容易想到。。那就是空间换时间。。因为它最普遍所以它也最不容易被想到

那么又出现了上面那个问题二维数组行吗?由于二维数组非常地死板。。所以一定会在空间上就爆炸

那么我们仍然选择用STL容器数组,动态开点,释放内存(clear()),由于我们只需要取元素。。vector就可以满足我们的需求

我们每次合并的时候,如果是不同集合的东西。。(相同集合就不要管了。。免得重复统计),那么我们就选择把数量小的森林

合并到大的森林里去。。然后把小森林的vector clear掉,

那么我一开始想的是这个事情很难实现。。原因是我考虑了这样一种情况。。如果合并的两颗森林里。。某一颗森林还包含了链向其他

编号的森林(因为会合并嘛),那你用vector怎么合并呢。。这个问题第一不好存储。。第二不好合并。。

但是你仔细想一下。。如果你从第一次开始。。每次都合并。。那么在vector里面这个就是线性的结构。集合里每个元素的父亲都严格

相等,那么每次都是线性的。。后面就不会有这种情况啊

Tips:我经常会问自己这样的问题。。那么以后在想的时候一定还记得要思考。。我思考的这个样例在什么条件下成立。。条件有可能被满足吗

在当前想法的前提下。。是否会出现这种状况。。走什么样的流程会导致这样的样例。。这个流程合法吗

不符合题意。。不符合条件的样例毫无意义。。会把你困死。。所以每当困惑的时候我们要找出出现困惑样例的前提条件,周遭的主要联系和关键

不要老是复杂化一个问题

Tips:实现在Mix里的vector合并有一个经典的错误。。通过size是否等于0来判断它是不是一个新的没计算过的集合。。,那么我们还要考虑有没有

相同的情况,但是当你第一次写下这个判断的时候当前发生的语句还真没有歧义。。当你合并完。。集合。。每一个集合clear了。。那么它的size就变

成了0,但是它并不是新的没有计算过的集合。。那么你应该马上意识到这一点。。并重新考虑判断条件。。并且你引入的新的辅助判断条件仍然不能有

歧义。。并且你还要时刻小心后面新加的操作仍然对判断语句造成歧义

合并之后我们就可以按照一个集合一个集合的顺序来统计答案,那么遍历的时候如何统计呢。。如果用cnt[maxn]。。每次都要情况。。

这样就又是n方了。。那么这里又有一个技巧。。

Tips:clear是要比memset快的。。(这个题来说),当我们发现有类似的矛盾可以尝试用clear或者其他动态开点的手段来解决

所以我们用一个map来统计颜色。。每统计完一个我们就清空map..因为map只需要清空很少的值。。而cnt不管如何都要强行全部清零

不过在这里我又想到了一种方法。。那就是记录一下都用到了哪些颜色。。我们只对这些元素进行清零。。(但是还是clear稳一点感觉)

下面贴上代码

#include <iostream>
#include <cstdio>
#include <vector>
#include <map> using namespace std;
int n,m,k;
const int maxn=2e5+;
int c[maxn],fa[maxn];
vector<int> Set[maxn];
map<int,int> mp;
void init(){
int i;
for(i=;i<=n;++i){
fa[i]=i;
}
}
int getFa(int v){
return (v==fa[v])?v:(fa[v]=getFa(fa[v]));
}
void Mix(int x,int y){
int a=getFa(x);
int b=getFa(y);
if(a!=b){
int sz1=Set[a].size();
int sz2=Set[b].size();
if(sz2>sz1) swap(a,b);
if(Set[a].size()==) Set[a].push_back(a);
if(Set[b].size()==) Set[a].push_back(b);
for(int i=;i<Set[b].size();++i){
Set[a].push_back(Set[b][i]);
}
Set[b].clear();
fa[b]=a;
}
}
int main(){
scanf("%d%d%d",&n,&m,&k);
int i,j;
for(i=;i<=n;++i){
scanf("%d",&c[i]);
}
int l,r;
init();
for(i=;i<=m;++i){
scanf("%d%d",&l,&r);
Mix(l,r);
}
int sum=;
for(i=;i<=n;++i){
int mx=;
for(j=;j<Set[i].size();++j){
int t=Set[i][j];
// printf("I:%d t:%d\n",i,t);
mp[c[t]]++;
mx=max(mx,mp[c[t]]);
}
sum+=Set[i].size()-mx;
mp.clear();
}
printf("%d\n",sum);
return ;
}

下面是直接利用邻接表连边。。构造出森林。。然后直接dfs遍历关系的集合森林。。dfs的先序过程中即可统计答案

这也算是并查集的另外一种实实在在的结构。。并查集能转化为邻接表的树。。那么森林也是可以表示并查集的。。相互转化

下面贴上代码

#include <iostream>
#include <cstdio>
#include <map>
#include <vector>
using namespace std;
const int maxn=2e5+;
int n,m,k;
int c[maxn];
map<int,int> mp;
vector<int> edge[maxn];
bool vis[maxn];
int mx,all;
void dfs(int v){
int i;
mp[c[v]]++;all++;mx=max(mx,mp[c[v]]);
for(i=;i<edge[v].size();++i){
int t=edge[v][i];
if(!vis[t]){
vis[t]=true;
dfs(t);
}
}
}
int main(){
scanf("%d%d%d",&n,&m,&k);
int i;
for(i=;i<=n;++i){
scanf("%d",&c[i]);
}
int l,r;
for(i=;i<=m;++i){
scanf("%d%d",&l,&r);
edge[l].push_back(r);
edge[r].push_back(l);
}
int sum=;
for(i=;i<=n;++i){
if(vis[i]) continue;
vis[i]=true;
all=;mx=;
dfs(i);
sum+=all-mx;
mp.clear();
}
printf("%d\n",sum);
return ;
}

按这种写法在dfs(i)之前我们就应该vis[i]=true;

如果仅仅是为了防止死循环。。那我们可以判断!=fa来防止dfs死循环

CF731C Socks并查集(森林),连边,贪心,森林遍历方式,动态开点释放内存的更多相关文章

  1. [bzoj3123][sdoi2013森林] (树上主席树+lca+并查集启发式合并+暴力重构森林)

    Description Input 第一行包含一个正整数testcase,表示当前测试数据的测试点编号.保证1≤testcase≤20. 第二行包含三个整数N,M,T,分别表示节点数.初始边数.操作数 ...

  2. CodeForces 731C C - Socks 并查集

    Description Arseniy is already grown-up and independent. His mother decided to leave him alone for m ...

  3. Codeforces 731C Socks 并查集

    题目:http://codeforces.com/contest/731/problem/C 思路:并查集处理出哪几堆袜子是同一颜色的,对于每堆袜子求出出现最多颜色的次数,用这堆袜子的数目减去该值即为 ...

  4. Codeforces Round #376 (Div. 2) C. Socks —— 并查集 + 贪心

    题目链接:http://codeforces.com/contest/731/problem/C 题解: 1.看题目时,大概知道,不同的袜子会因为要在同一天穿而差生了关联(或者叫相互制约), 其中一条 ...

  5. 【转】并查集&MST题集

    转自:http://blog.csdn.net/shahdza/article/details/7779230 [HDU]1213 How Many Tables 基础并查集★1272 小希的迷宫 基 ...

  6. 《数据结构与算法分析:C语言描述》复习——第八章“并查集”——并查集

    2014.06.18 14:16 简介: “并查集”,英文名为“union-find set”,从名字就能看出来它支持合并与查找功能.另外还有一个名字叫“disjoint set”,中文名叫不相交集合 ...

  7. 【bzoj4399】魔法少女LJJ 并查集+权值线段树合并

    题目描述 在森林中见过会动的树,在沙漠中见过会动的仙人掌过后,魔法少女LJJ已经觉得自己见过世界上的所有稀奇古怪的事情了LJJ感叹道“这里真是个迷人的绿色世界,空气清新.淡雅,到处散发着醉人的奶浆味: ...

  8. HDU 3047 带权并查集 入门题

    Zjnu Stadium 题目链接 http://acm.hdu.edu.cn/showproblem.php?pid=3047 Problem Description In 12th Zhejian ...

  9. poj1611(并查集)

    题目链接:http://poj.org/problem?id=1611 题意: SARS(非典型肺炎)传播得非常厉害,其中最有效的办法是隔离那些患病.和患病者接触的人.现在有几个学习小组,每小组有几个 ...

随机推荐

  1. json格式

    $.post('text.action',{....},function(datas){ var name=datas.data[0].name; }); 如果是多个还可以用循环获取.$.post(' ...

  2. Mathematics:Dead Fraction(POJ 1930)

    消失了的分式 题目大意:某个人在赶论文,需要把里面有些写成小数的数字化为分式,这些小数是无限循环小数(有理数),要你找对应的分母最小的那个分式(也就是从哪里开始循环并不知道). 一开始我也是蒙了,这尼 ...

  3. 经典排序算法 - 冒泡排序Bubble sort

    原理是临近的数字两两进行比较,按照从小到大或者从大到小的顺序进行交换, 这样一趟过去后,最大或最小的数字被交换到了最后一位, 然后再从头开始进行两两比较交换,直到倒数第二位时结束,其余类似看例子 例子 ...

  4. CCF I'm Stuck!

    问题描述 试题编号: 201312-5 试题名称: I'm stuck! 时间限制: 1.0s 内存限制: 256.0MB 问题描述: 问题描述 给定一个R行C列的地图,地图的每一个方格可能是'#', ...

  5. 51nod 1070 Bash游戏 V4 (斐波那契博弈)

    题目:传送门. 有一堆个数为n(n>=2)的石子,游戏双方轮流取石子,规则如下: 1)先手不能在第一次把所有的石子取完,至少取1颗: 2)之后每次可以取的石子数至少为1,至多为对手刚取的石子数的 ...

  6. 会话控制(session、cookie)

    1.session(1)session存储在服务器的(2)session每个人存一份(3)session有默认的过期时间(4)session里面可以存储任意类型的数据安全,对服务造成压力用法:1.当一 ...

  7. 基于Spring的可扩展Schema进行开发自定义配置标签支持

    一.背景 最近和朋友一起想开发一个类似alibaba dubbo的功能的工具,其中就用到了基于Spring的可扩展Schema进行开发自定义配置标签支持,通过上网查资料自己写了一个demo.今天在这里 ...

  8. ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(六) 之 Layim源码改造右键菜单--好友、组管理功能的实现。

    前言 上一篇中讲解了加好友的流程,本篇将介绍好友管理,群组管理的右键菜单功能.当然由于菜单项目太多,都实现也得花费时间.只讲解一下我是如何从不知道怎么实现右键菜单到会自定义菜单的一个过程.另外呢,针对 ...

  9. UINavigationController导航条是否挡住下面的内容

    控制 UINavigationController 导航条是否挡住下面的内容 if ([[[UIDevice currentDevice] systemVersion] floatValue] > ...

  10. Linux USB Project

    转自:http://www.linux-usb.org/ Welcome to the home of the Linux USB Project This web site was created ...