codeforces round 895(div.3) F.Selling a Menagerie 详解

codeforces round 895(div.3) F.Selling a Menagerie 详解

这个题就是拓扑排序+环处理

首先题意就是有n种动物,每个动物都有自己害怕的动物对象,如果这个动物在自己的害怕对象之前卖出,价钱就是本来的价格c的2倍,如果在自己的害怕对象之后卖出就本来的价格c,目的就是让能卖出的总价钱最大。

所以最优的结果就是尽可能让每种动物都在自己的害怕对象之前卖出,是不是很像拓扑排序中尽可能先让入度为0的点先输出。也就是按照该动物指向该动物的害怕对象的顺序建立有向图。

前面的都好想,就是拓扑排序模版处理不成环的动物,但是这里有n个动物,之间有n个关系,就一定存在环,所以接下来还要处理环。

在环处理中要注意两点

1.对于单个环来说,应该从价钱最小的动物的下一个(也就是它的害怕对象)开始,让价钱最小的点最后一个卖出,这样就只有最小值没有*2,即为最优结果。

2.一定要想到有多个环存在的可能(我就是没注意的这一点,前面几次都没过),n个动物n个害怕关系,一定存在环,但是不一定只有一个环,可能不连通。

处理多个环的时候,不需要dfs去查询到底有多少个环,先按动物的价钱进行排序,再按这个顺序遍历所有的动物,在拓扑排序中访问过的直接标记跳过,没标记过的就从它的害怕对象开始访问这个环即可,这里不用在乎环与环之间的先后顺序,因为环跟环之间的动物没有任何关系。

代码如下(感觉代码的注释写的更清楚一点)

#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
int t;  //a数组用来存害怕对象,d是拓扑排序中存该点入度的数量  
int a[100009],d[100009],vis[100009]; 
struct aa      //单独开一个结构体存价钱
{
    int index;  //下标
    int w;      //价钱
}w[100009];   //存边值的时候要使用结构体 保留下标信息 后面要排序会打乱
int first[100009];   //链式前向星 存关系图
struct edge
{
    int to;    
    int next;
}e[100009];   //边
int cent;
void add(int u,int v)   
{
    cent ++;
    e[cent].to = v;
    e[cent].next = first[u];
    first[u] = cent;
}
bool cmp(aa a,aa b)  //后面对价钱排序会用到
{
    return a.w<b.w;
}
int main()
{
    cin>>t;
    while(t--)
    {
        memset(first,0,sizeof(first));
        memset(d,0,sizeof(d));
        memset(vis,0,sizeof(vis));
        cent = 0;
        int n;
        cin>>n;
        for(int i=1;i<=n;i++)
        {
            cin>>a[i];
            add(i,a[i]);  //存入关系 该动物指向它的害怕对象
            d[a[i]] ++;   //入度++
        }
        for(int i=1;i<=n;i++)   //存价钱
        {
            cin>>w[i].w;
            w[i].index = i;  
        }
        //先用拓扑排序解决不成环的 最优结果
        queue<int> q;
        int num = 0;    //记录输出个数 
        for(int i=1;i<=n;i++)   //先找入度为0的(也就是没有人害怕它)
        {
            if(d[i]==0) q.push(i);
        }
        while(!q.empty())
        {
            int x = q.front();
            q.pop();
            vis[x] = 1;   //标记已经访问过
            num ++;
            cout<<x<<" ";
            for(int i=first[x];i;i=e[i].next)
            {
                int to = e[i].to;
                d[to] --;
                if(d[to]==0) q.push(to);
            }
        }
        //如果num下于n 也就是存在环
        if(num<n)   //处理环
        {    
            sort(w+1,w+n+1,cmp);    //先对所有剩下的边从小到大排序 
            for(int i=1;i<=n;i++)   //for保证遍历所有的环 这个环可能不止一个
            {                      //只需要保证同一个环中的有序即可 环与环之间的顺序并不重要 它们之间并没有害怕的关系
                int to = w[i].index;  //最小动物的下标 
                if(vis[to]) continue;   //如果在前面的拓扑排序中已经访问过了 就不需要再访问了
                while(vis[e[first[to]].to]==0)  //这里是找最小动物的下标的下一个 从这个最小的下一个开始
                {                               //也就是让最小动物变成该环中最后一个出去的 让最小的不乘2 即让结果最大
                    cout<<e[first[to]].to<<" ";   
                    to = e[first[to]].to;        //遍历这个环
                    vis[to] = 1;
                }
            }
        }
    }
    return 0;
}

虽然看到很多人说这题很简单,而且也不是在比赛的时候写出来的,也是在得到同学的小小提示下写出来的(让我领悟到原来不止一个环),但是这真的是我第一次能写出来f题,自己还是挺开心的,加油加油,我现在还是好菜啊,真希望自己能变厉害一点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值