题解:P11811 [PA 2015] 人赢 / Mistrzostwa
废话
蒟蒻的第一篇题解!
正文
(内含一组 hack,如果你只 WA 第 18 个点)。
楼上的各位大佬,讲题思路已经很详细了。
因此这篇题解主要的目的是讲几个易错点。
那就看看我的“死亡回放”吧。
错误一
30pts。
死亡原因:没读题。
我没看见有两个问号……所以只输出了集合大小。
因此我花费了一次宝贵的测试点下载机会。
下载了个样例。
那三十分纯粹就是“不可以,总司令!”。
然后就能得 30pts。
(希望这个神金的死因能让你笑一下)。
修改后代码:(非 AC!!)。
//https://www.luogu.com.cn/problem/P11811
//P11811 [PA 2015] 人赢 / Mistrzostwa
#include<iostream>
#include<queue>
#define maxn 200010
#define maxm 400010
using namespace std;
int out[maxn],vis[maxn];
struct EDGE
{
int to,next;
}edge[maxm];
int tot=0,head[maxn];
void add(int u,int v)
{
edge[++tot].to=v;
edge[tot].next=head[u];
head[u]=tot;
}
queue <int> q;
int main()
{
int n,m,d;
cin>>n>>m>>d;
for(int i=1;i<=m;i++)
{
int a,b;
cin>>a>>b;
add(a,b);
add(b,a);
out[a]++;
out[b]++;
}
int ans=n;
for(int i=1;i<=n;i++)
if(out[i]<d)
{
q.push(i);
vis[i]=1;
}
while(!q.empty())
{
ans--;
int p=q.front();
q.pop();
for(int i=head[p];i;i=edge[i].next)
{
int v=edge[i].to;
if(vis[v]==1)continue;
out[v]--;
if(out[v]<d)
{
q.push(v);
vis[v]=1;
}
}
}
if(ans==0)cout<<"NIE";
else
{
cout<<ans<<"\n";
for(int i=1;i<=n;i++)if(vis[i]==0)cout<<i<<" ";
}
return 0;
}
/*
4 4 2
1 2
2 3
3 4
4 2
3
2 3 4
*/
错误二
相信大犇们都能看出来我这个代码有大大滴问题。
所以只能得 76pts。
一个很明显的死因就是:
这张图并不一定联通。
所以我不能直接在 n 的基础上进行加减。
然后,我才想到本题的主角:并查集。
于是我就在建图的时候先连并查集。
后来对每一个集合进行类似处理。
之后得到的代码就是这个(非 AC!!!!)。
//https://www.luogu.com.cn/problem/P11811
//P11811 [PA 2015] 人赢 / Mistrzostwa
#include<iostream>
#include<queue>
#define maxn 200010
#define maxm 400010
using namespace std;
int out[maxn],vis[maxn],fa[maxn],num[maxn];
struct EDGE
{
int to,next;
}edge[maxm];
int tot=0,head[maxn];
void add(int u,int v)
{
edge[++tot].to=v;
edge[tot].next=head[u];
head[u]=tot;
}
int find(int x){return (fa[x]==x?x:fa[x]=find(fa[x]));}
void marge(int x,int y)
{
x=find(x);
y=find(y);
if(x==y)return;
fa[y]=x;
}
queue <int> q;
int main()
{
int n,m,d;
cin>>n>>m>>d;
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=1;i<=m;i++)
{
int a,b;
cin>>a>>b;
marge(a,b);
add(a,b);
add(b,a);
out[a]++;
out[b]++;
}
for(int i=1;i<=n;i++)
{
num[find(i)]++;
}
for(int i=1;i<=n;i++)
if(out[i]<d)
{
q.push(i);
vis[i]=1;
}
while(!q.empty())
{
int p=q.front();
num[find(p)]--;
q.pop();
for(int i=head[p];i;i=edge[i].next)
{
int v=edge[i].to;
if(vis[v]==1)continue;
out[v]--;
if(out[v]<d)
{
q.push(v);
vis[v]=1;
}
}
}
int ans=0,maxnum=0;
for(int i=1;i<=n;i++)
{
if(num[i]>ans)ans=num[i],maxnum=i;
}
if(ans==0)return cout<<"NIE",0;
cout<<ans<<"\n";
for(int i=1;i<=n;i++)
if(find(i)==maxnum&&vis[i]==0)
cout<<i<<" ";
return 0;
}
/*
4 2 1
4 1
2 3
2
1 4
*/
这其实是一个非常优的骗分解。
简直是我见过写过的最优的骗分解。
因为它可以骗到 97pts 的高分!!
错误三
可是您是要 AKIOI 的人!
不允许有一分的丢失!
大犇们看我代码也看出来了。
我这个代码有个非常严重的问题。
就是导出子图不一定联通。
hack
举个例子:

比如这张图 d = 3。
那么扫描一遍就会把中间的那个店删掉。
之后在扫描,就会发现所有点都“合法”。
于是程序就会把剩下并不联通的八个点一起输出了。
那通过这个,我们就能看出:
在删边前就维护集合是行不通的。
因为断边之后,集合的连通性会发生改变,而你却不自知。
又因为断边维护集合是困难的。
因此正确答案昭然若揭:
先断边,再跑并查集。
(不是这么简单的道理我咋没想到呢)
正解
正解我就不讲了。
楼上各位大犇讲得都很详细。
那我就直接扔代码了。
(AC)
Talk is cheap, show me the code.
//https://www.luogu.com.cn/problem/P11811
//P11811 [PA 2015] ÈËÓ® / Mistrzostwa
#include<iostream>
#include<queue>
#define maxn 200010
#define maxm 400010
using namespace std;
int out[maxn],vis[maxn],fa[maxn],num[maxn];
struct EDGE
{
int to,next;
}edge[maxm];
int tot=0,head[maxn];
void add(int u,int v)
{
edge[++tot].to=v;
edge[tot].next=head[u];
head[u]=tot;
}
int find(int x){return (fa[x]==x?x:fa[x]=find(fa[x]));}
void marge(int x,int y)
{
x=find(x);
y=find(y);
if(x==y)return;
fa[y]=x;
}
queue <int> q;
int main()
{
int n,m,d;
cin>>n>>m>>d;
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=1;i<=m;i++)
{
int a,b;
cin>>a>>b;
add(a,b);
add(b,a);
out[a]++;
out[b]++;
}
for(int i=1;i<=n;i++)
if(out[i]<d)
{
q.push(i);
vis[i]=1;
}
while(!q.empty())
{
int p=q.front();
q.pop();
for(int i=head[p];i;i=edge[i].next)
{
int v=edge[i].to;
if(vis[v]==1)continue;
out[v]--;
if(out[v]<d)
{
q.push(v);
vis[v]=1;
}
}
}
for(int i=1;i<=n;i++)
{
if(vis[i]==1)continue;
for(int j=head[i];j;j=edge[j].next)
{
int t=edge[j].to;
if(vis[t]==1)continue;
marge(i,t);
}
}
for(int i=1;i<=n;i++)
if(vis[i]==0)
num[find(i)]++;
int ans=0,maxnum=0;
for(int i=1;i<=n;i++)
{
if(num[i]>ans)ans=num[i],maxnum=i;
}
if(ans==0)return cout<<"NIE",0;
cout<<ans<<"\n";
for(int i=1;i<=n;i++)
if(find(i)==maxnum&&vis[i]==0)
cout<<i<<" ";
return 0;
}
/*
4 2 1
4 1
2 3
2
1 4
*/
本文来自博客园,作者:永韶,转载请注明原文链接:https://www.cnblogs.com/yongshao/p/19313741

浙公网安备 33010602011771号