#BJTUOJ 铁憨憨骑士的小队分配(图论缩点+思维)

3 篇文章 0 订阅
2 篇文章 0 订阅

1.原题

原题链接.

在遥远的憨憨王国,有一个铁憨憨骑士团。骑士团中有 n 位骑士。

为了使骑士们团结互助、尽可能发挥集体的战斗力,骑士团规定,每位骑士必须成为某一位骑士的“守护骑士”,遇到危险时优先保护他。

每位骑士都至少要被一位骑士守护。显然,骑士不能守护自己。

骑士团的团长有一天心血来潮,决定将骑士们分成若干个小队。有强迫症的团长对分队方法有着自己的一套要求:

1、每个骑士都不能和自己的守护骑士在同一个小队中;

2、如果有两个骑士在同一个小队中,并且守护了两个不同的骑士,那么他们守护的那两个骑士也必须在同一个小队中。

举例来说,如果骑士A守护骑士B,骑士C守护骑士D,A和C在同一个小队,那么B和D必须在同一个小队,并且这个小队不能是A和C所在的那个小队。

因为小队长的工资很高,为了节省开销,团长找到了你,想知道在满足以上条件的情况下,骑士团至少需要分成几个小队。

输入数据
第一行一个整数 n(2≤n≤500),表示骑士团中骑士的数量。
第二行 n 个整数,以空格隔开,第 i 个数字 ai 表示编号为 i 的骑士守护了编号为 ai 的骑士。
输入数据保证 i≠ai。

输出数据
一行一个整数,表示骑士团至少需要分成几个小队。

样例输入
9
6 7 1 9 8 3 4 2 5
样例输出
3
样例说明
只需要分成3个小队,第一个小队有1、2、9号骑士,第二个小队有5、6、7号骑士,第三个小队有3、4、8号骑士即可。

2.题意

若干个人,每个人必须被至少另一个人守护。现在需要给这些人分组,使得1.每组中任意两个人不存在守护关系,2.某一组中所有人的守护者都同在另一组中,且所有人守护的人也同在一个组。

3.思路

从图论角度思考,若a守护b则从a向b连一条有向边。本题变成了:给若个点分组使得1.每一组各点不存在边,2.某一组中所有点指向的点共同在一组中,且指向该组中所有点的点共同在一组中。
然后我们从输入发现:一个人只能守护唯一一个骑士,且每个人必须被一个人守护,所以必然有每个点入度、出度均为1.(这题把隐含条件藏在了输入中,够狠)
于是发现这个图必然构成了若干个环(不包括自环),发现分组就是缩点,我们只需给这个图的点分组(缩点),求新图点数最小值。
接着我们可以发现几个性质:
性质1:给点分组后,把每组看成点(缩点),形成的新图仍是若干环
性质2:对于某个环,取他的任意一个非1因数,就是其新环的一种可能点数。比如,某个环有6个点,取因数为2,那么考虑把六个点分成两组,按照序号从1到6排列,第一组是序号除以2余1的,另一组是序号除以2余0的,显然符合题意。
性质3:两个环点数具有公因数,可以把这两个环缩点形成的新环合二为一。比如,一个环6个点,另一个环9个点,公因数为3,所以按性质2分别按照除以3的余数给两个环的点各分为3组,形成2个长度为3的环,可以证明这两个环可以变为一个长度为3的环。
性质4.:新成的环的点数必然是质数个。(因为本题要求的是缩点后环上最小点数,只要不是质数,就可以按照性质2取其因数进行缩点)
打表发现:1~500的质数并不多,且和为500的不同质数最多有10几个,意味着递归深度很小,这么一来,就变成了一个组合问题,我们就可以考虑dfs枚举,选了哪些质数。
具体做的时候,可以加一个优化:根据性质3,假如某个数的因数包括之前选过的质因数,可直接跳过!

4.代码

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <queue>
#include <malloc.h>
#include <unordered_map>

using namespace std;
typedef long long LL;
const int N=505;

int n,con[N],num[N],cnt,ans;//num数组维护每个环的点数,con数组本题没用,哈哈。
bool g[N][N],st[N];//范围足够小,可以用邻接矩阵。
vector<int> v;

void dfs(int u,int &number)
{
    number++;
    st[u]=1;
    con[u]=cnt;
    for(int i=1;i<=n;i++)
    {
        if(g[u][i]&&!st[i])dfs(i,number);
    }
}

void floodfill()//找环
{
    for(int i=1;i<=n;i++)
    {
        if(st[i])continue;
        //cout<<i<<endl;
        cnt++;
        int number=0;
        dfs(i,number);
        num[cnt]=number;
    }
}

void solve(int u,int temp)
{
    if(u>cnt)
    {
        ans=min(ans,temp);
        return;
    }
    for(int i:v)
    {
        if(num[u]%i==0)
        {
            if(st[i])solve(u+1,temp);
            else
            {
                st[i]=true;
                solve(u+1,temp+i);
                st[i]=false;
            }
        }
    }
    return;
}

int main()
{
    cin>>n;
    ans=n;
    for(int i=1;i<=n;i++)
    {
        int x;
        cin>>x;
        g[i][x]=1;
    }
    v.push_back(2);
    for(int i=3;i<=n;i++)
    {
        bool flag=true;
        for(int j:v)
        {
            if(i%j==0)
            {
                flag=false;
                break;
            }
        }
        if(flag)v.push_back(i);
    }

    floodfill();

    memset(st,0,sizeof st);
    solve(1,0);
    cout<<ans<<endl;
}

5.收获

1.每个点入度和出度为1,说明一个图由若干个环构成
2.质数具有很好的枚举潜力(懂得都懂)
3.题目隐含条件可能在输入输出中。
4.多模拟,才能发现性质

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值