题解: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
*/

posted @ 2025-12-05 21:56  永韶  阅读(0)  评论(0)    收藏  举报