CF1872F Selling a Menagerie 的题解
题面大意
动物园里有 个动物,第 个动物害怕第 个动物,第 个动物价值 元。现在我要将这些动物全部卖掉。显然,卖掉的动物编号可以构成一个排列 。
考虑卖掉这些动物时:
- 若 在 还没有卖掉之前就被卖掉了,现在卖掉 ,可以获得 元;
- 若 在 还没有卖掉之前没被卖掉,现在卖掉 ,可以获得 元;
构造并输出赚钱最多的动物卖出顺序。
思路
显而易见,题目可以转化为图论进行解决。
将第 个点向 连一条无向边,构成一个 个点 条边的基环树森林。
如果并没有其他的动物害怕第 只动物,那么将第 只动物卖出后并不会因为缺少了自己害怕的动物而减少价值。反映在图中即是将没有入度的点依次卖出,就是跑一次拓扑序。
因为这个图是一个基环树森林,所以在将所有的入度为 的点全部删除之后剩下的节点必然满足对于任意的 与 有且仅有一条边将他们链接。
我们可以贪心的考虑删除掉环中的那一个点。
假设节点 害怕的动物为 ,其价格为 ,这个环内的节点为 ,那么肯定有一个动物会因为自己害怕的动物已经卖出而变得便宜,不放假设变便宜的动物为 。
这个环的价值就为 。因为 是一个定值,所以要使价值最大化就应该使 尽可能的小。如果提前卖掉的动物为为 那么 ,所以提前删除的点 应该使 最小。
在每一次将环内的一个点卖出后,剩下的节点就可以通过拓扑序的方式依次删除。
注意,因为这是一个基环树森林,所以可能存在多个环。
AC Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
int n,du[N],s[N];
vector<int> v[N];
queue<int> qq;
bool vis[N];
struct node{
int x,v;
friend bool operator < (const node a,const node b){
return a.v>b.v;
}
};
priority_queue<node>q;
void solve(){
cin>>n;
int cnt=0;
for(int i=1;i<=n;i++){
v[i].clear();
du[i]=0;
vis[i]=0;
s[i]=0;
}
while(!q.empty()){
q.pop();
}
for(int i=1,x;i<=n;i++){
cin>>x;
du[x]++;
v[i].push_back(x);
}
for(int i=1;i<=n;i++){
cin>>s[i];
}
for(int i=1;i<=n;i++){
if(du[i]==0){
qq.push(i);
cnt++;
vis[i]=1;
cout<<i<<" ";
}
}
while(!qq.empty()){
int x=qq.front();
qq.pop();
for(int i:v[x]){
du[i]--;
if(du[i]==0){
cout<<i<<" ";
qq.push(i);
vis[i]=1;
cnt++;
}
}
}
if(cnt==n){
return;
}
for(int i=1;i<=n;i++){
if(vis[i]){
continue;
}
int p=0;
while(vis[v[i][p]]) p++;
q.push({v[i][p],s[i]});
}
while(cnt<n){
while(vis[q.top().x]) q.pop();
qq.push(q.top().x);
cnt++;
vis[q.top().x]=1;
cout<<q.top().x<<" ";
while(!qq.empty()){
int x=qq.front();
qq.pop();
for(int i:v[x]){
du[i]--;
if(du[i]==0&&!vis[i]){
cout<<i<<" ";
qq.push(i);
cnt++;
vis[i]=1;
}
}
}
}cout<<endl;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin>>t;
while(t--){
solve();
}
return 0;
}