题目大意:给定一张无向图,一个点集能分成两部分当且仅当这两部分的每一对点之间都有边,求最多能分成多少部分以及每部分的大小
这实际上就是在求反图的联通块个数以及每个联通块大小
但是反图太大,不能直接做
因此我们枚举每个点,从这个点开始DFS
这样做是O(n^2)的,但是我们可以利用并查集来减少枚举的复杂度
一个点x如果访问过,就在并查集中连向x+1 这样做的好处是枚举的时候可以直接找到下一个没有访问过的点
然后证一下复杂度吧
定义函数Φ为当前未访问过的点数和未访问过的边数之和
初始Φ=n+m,显然任意时刻Φ>=0
每枚举到一个点y,如果x没有向y的边,那么未访问过的点数-1,故Φ会-1
如果x有向y的边,那么未访问过的边数-1,故Φ会-1
因此最终DFS的复杂度为O(n+m)
然后就随便搞了
珍爱生命,远离STL
#include <vector>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define M 100100
using namespace std;
int n,m,ans;
int cnt[M];
vector<int> f[M];
namespace Union_Find_Set{
int fa[M];
int Find(int x)
{
if(!fa[x]||fa[x]==x)
return fa[x]=x;
return fa[x]=Find(fa[x]);
}
}
namespace IStream{
#define L (1<<15)
char buffer[M],*S,*T;
char Get_Char()
{
if(S==T)
{
T=(S=buffer)+fread(buffer,1,L,stdin);
if(S==T) return EOF;
}
return *S++;
}
int Get_Int()
{
int re=0;
char c=Get_Char();
while( c<'0' || c>'9' )
c=Get_Char();
while( c>='0' && c<='9' )
re=(re<<1)+(re<<3)+(c-'0'),c=Get_Char();
return re;
}
}
using namespace Union_Find_Set;
void DFS(int x)
{
int i;
cnt[ans]++;
fa[x]=Find(x+1);
vector<int>::iterator it;
for(it=f[x].begin(),i=Find(1);i<=n;i=Find(i+1))
{
for(;it!=f[x].end()&&*it<i;it++);
if(*it==i) continue;
DFS(i);
}
}
int main()
{
using namespace IStream;
int i,x,y;
cin>>n>>m;
for(i=1;i<=m;i++)
{
x=Get_Int();
y=Get_Int();
f[x].push_back(y);
f[y].push_back(x);
}
for(i=1;i<=n;i++)
sort(f[i].begin(),f[i].end());
for(i=1;i<=n;i++)
if(Find(i)==i)
++ans,DFS(i);
sort(cnt+1,cnt+ans+1);
cout<<ans<<endl;
for(i=1;i<=ans;i++)
printf("%d ",cnt[i]);
return 0;
}