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.多模拟,才能发现性质