思路:
这个题比较容易想到的是求最小环,还有就是并查集。但是很难想到如何处理。
我们把它一步步来做,因为有人数限制,然后我们又能将人群分成一个又一个的集合,所以可以想到背包问题。然后,我们要如何去划分这一个个集合?
我们首先要找出每一个集合最小的情况。因为它的出度只能是 1,所以不可能形成两个环相交的情况,只能是在形成一个环后,又会有一些零散的点连在这个环上。于是,我们可以求出这个最小环作为选择这个集合就必须要加的最小值,然后再通过并查集求出这个集合可以加的最大值。并且在min~max这个区间中每个值都能取到。对于这一个个的集合求出满足客车容量的最大加和(分组背包)。
因此我们要先去求最小环,用tarjan。(一个灰常不错的视频:tarjan学习链接)然后通过并查集处理,最后分组背包。、
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e4+5;
#define int long long
typedef pair<int, int>PII;
int n, m, k;
int T;
int a[N];
vector<int>v[N];
int dfn[N], low[N], sta[N];
int cnt, top;
bool st[N];
int id[N], sz[N], tt;
int p[N];
map<int, PII>mp;
int f[N];
void tarjan(int u)
{
dfn[u] = low[u] = ++cnt;
sta[top++] = u; //入栈
st[u] = true; //标记,在栈中
for(int i = 0; i < v[u].size(); i ++)
{
int t = v[u][i];
if(!dfn[t])
{
tarjan(t);
low[u] = min(low[u], low[t]);
}
else if(st[t])
{
low[u] = min(low[u], low[t]);
}
}
if(dfn[u]==low[u])
{
tt++;
while(1)
{
int y = sta[top-1];
top--;
id[y] = tt; //将同一个环中的元素设置相同的编号
sz[tt] ++; //并且记录相同编号的集合中元素个数
st[y] = false;
if(y==u) break;
}
}
}
int find(int x)
{
if(x != p[x]) p[x] = find(p[x]);
return p[x];
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> m;
for(int i = 1; i <= n; i ++)
{
cin >> a[i];
v[i].push_back(a[i]); //建图
}
for(int i = 1; i <= n; i ++)
{
if(!dfn[i]) tarjan(i); //缩点
}
//并查集
for(int i = 1; i <= n; i ++) p[i] = i;
for(int i = 1; i <= n; i ++)
{
int x = id[i], y = id[a[i]];//如果不在一个集合,就加到同一个集合中
if(find(x) != find(y))
p[find(x)] = find(y);
}
for(int i = 1; i <= n; i ++)
{
//求出分组背包的最小值,这个题中分组背包的结构是一个环,然后会有一个个点连在环上(这一个个点可能会形成链,但不会形成环)
//所以最小值是这个环本身,最大值是并查集得到的这个集合。并且min~max这个区间中,任何一个值都能被取到,因为是一个个的点加到环上去的
mp[find(i)].first = max(mp[find(i)].first, sz[i]);
mp[find(i)].second += sz[i];//分组背包的最大值
}
//分组背包(模板)
for(auto z : mp)
{
for(int i = m; i >= 0; i --)
{
for(int j = z.second.first; j <= z.second.second; j ++)
{
if(i >= j) f[i] = max(f[i], f[i - j] + j);
}
}
}
cout << f[m] << "\n";
return 0;
}