看了以后第一反应是图论。
实在不好意思瞟了一眼题解:二分图匹配。然后我就想啦,怎么匹配呢?
显然,对应的属性要选择不同的装备,所以只要在相应的装备和属性之间连边,然后做一次二分图匹配就好了。注意,由于数字是连续的,所以一旦匹配不成功就要退出二分图匹配并输出此时的数字。
于是乎,像往常一样,写了一个朴素的匈牙利算法。TLE。试着用邻接表,邻接矩阵,各种存图的方法都T。
扫了一遍,感觉不知道怎么优化啊,于是看了一眼题解,看到了神犇写法:bitset,即用bitset代替bool数组,时间竟快了一倍。
#include<vector>
#include<bitset>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
int n;
bitset<1000002>vis;
int Link[1000002];
vector<vector<int> >w(10002);
bool find(int x){
for(int i=0;i<w[x].size();i++){
if(!vis[w[x][i]]){
vis[w[x][i]]=1;
if(!Link[w[x][i]]||find(Link[w[x][i]])){
Link[w[x][i]]=x;
return true;
}
}
}
return false;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
int a,b;
scanf("%d%d",&a,&b);
w[a].push_back(i);
w[b].push_back(i);
}
for(int i=1;i<=10001;i++){
//memset(vis,0,sizeof(vis));
vis=0;
if(!find(i)){
cout<<i-1<<endl;
return 0;
}
}
return 0;
}
题解中还有另一种方法:并查集。我觉得这个思维含量很高,看了很久的分析,现在让我独立的把它再分析一遍。
并查集的要求是保证祖先是这个连通分量中最大的点,并用flag[i]记录是否可以选择属性i。
1.每次读入a和b,fa和fb分别代表他的祖先。
(1).fa==fb,则无论原来的连通分量是树还是存在环,加入这条边以后一定是一个环,则每个点必定可以对应一条边,即每个属性必定和一对应一个装备。由于之前的点的flag已经更新过,所以只需flag[fa]=true即可;
(2).fa!=fb,则加入这条边以后是连接两个连通分量,且需要将祖先节点更新成max(fa,fb)。于是为了保证属性尽可能连续,所以应首先保证flag[min(fa,fb)]=1,且无论连成的连通分量是环还是树,都可以保证flag[min(fa,fb)]=1。如果之前flag[min(fa,fb)]已经为真,则说明连成的新的连通分量中肯定存在环,则可更新flag[max(fa,fb)]=1。
2.从1到10000遍历flag数组当flag[i]=0时,答案为i-1。
说的很复杂,代码很简单,如下:
#include<stdio.h>
#include<iostream>
#include<string>
#include<algorithm>
#define MAXN 1000002
using namespace std;
int f[MAXN];
int getf(int x){
return x==f[x] ? x : f[x]=getf(f[x]);
}
bool flag[10010];
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=10010;i++) f[i]=i;
for(int i=1;i<=n;i++){
int a,b;
scanf("%d%d",&a,&b);
int fa=getf(a);
int fb=getf(b);
if(fa==fb) flag[fa]=1;
else {
if(fa>fb) swap(fa,fb);
if(!flag[fa]) flag[fa]=1;
else flag[fb]=1;
f[fa]=fb;
}
}
int i;
for(i=1;i<=10010;i++) if(!flag[i]) break;
printf("%d",i-1);
return 0;
}