洛谷 P8269 [USACO22OPEN] Visits S

题目描述

Bessie 的 \(N\)(\(2\le N\le 10^5\))个奶牛伙伴(编号为 \(1\cdots N\))每一个都拥有自己的农场。对于每个 \(1\le i\le N\),伙伴 i 想要访问伙伴 \(a_i\)(\(a_i\neq i\))。

给定 \(1\ldots N\) 的一个排列 \((p_1,p_2,\ldots, p_N)\),访问按以下方式发生。

对于 \(1\) 到 \(N\) 的每一个 \(i\):

  • 如果伙伴 \(a_{p_i}\) 已经离开了她的农场,则伙伴 \(p_i\) 仍然留在她的农场。
  • 否则,伙伴 \(p_i\) 离开她的农场去访问伙伴 \(a_{p_i}\) 的农场。这次访问会产生快乐的哞叫 \(v_{p_i}\) 次(\(0\le v_{p_i}\le 10^9\))。

对于所有可能的排列 \(p\),计算所有访问结束后可能得到的最大哞叫次数。

输入格式

输入的第一行包含 \(N\)。

对于每一个 \(1\le i\le N\),第 \(i+1\) 行包含两个空格分隔的整数 \(a_i\) 和 \(v_i\)。

输出格式

输出一个整数,为所求的答案。

注意这个问题涉及到的整数可能需要使用 64 位整数型(例如,C/C++ 中的 "long long")。

输入输出样例 #1

输入 #1

4
2 10
3 20
4 30
1 40

输出 #1

90

说明/提示

【样例解释】

如果 \(p=(1,4,3,2)\),则

  • 伙伴 \(1\) 访问伙伴 \(2\) 的农场,产生 \(10\) 次哞叫。
  • 伙伴 \(4\) 看到伙伴 \(1\) 已经离开了农场,所以无事发生。
  • 伙伴 \(3\) 访问伙伴 \(4\) 的农场,又产生 \(30\) 次哞叫。
  • 伙伴 \(2\) 看到伙伴 \(3\) 已经离开了农场,所以无事发生。

这样总计得到了 \(10+30=40\) 次哞叫。

另一方面,如果 \(p=(2,3,4,1)\),则

  • 伙伴 \(2\) 访问伙伴 \(3\) 的农场,产生 \(20\) 次哞叫。
  • 伙伴 \(3\) 访问伙伴 \(4\) 的农场,产生 \(30\) 次哞叫。
  • 伙伴 \(4\) 访问伙伴 \(1\) 的农场,产生 \(40\) 次哞叫。
  • 伙伴 \(1\) 看到伙伴 \(2\) 已经离开了农场,所以无事发生。

这样总计得到了 \(20+30+40=90\) 次哞叫。可以证明这是所有可能的排列 \(p\) 中访问结束后得到的最大可能的哞叫次数。

算法分析

这题因为一只奶牛出去之后,就不会再回来,也就是每只奶牛只有一次访问的机会,一旦它出去了,那么可以理解为它失踪了,把它去掉。所以得出了这\(n\)只奶牛都会形成若干条链,根据这个特征,就可以写出最大生成树,时间复杂度为\(O(nlogn)\),可以通过。

坑点

  1. 最终形成的可以不是树,但一定由若干条链组成。
  2. 要跑的是最大生成树,不是最小,排序规则不要写错。
  3. 因为链中的每个节点最多有2条连边,不要写成这个条件,而是每只奶牛至多有一条出边,因为如果按前一个条件写,一只奶牛拜访两只奶牛是满足条件的,但在题目中是不合法的,所以第二种是正确的。
#include<bits/stdc++.h>
using namespace std;
struct node{
    int x,y,z;
};
bool cmp(node a,node b){//排序规则
    return a.z>b.z;
}
int n;
node a[200005];
int fa[200005];
int cnt[200005];//记录出边
long long ans = 0;
int find(int x){
    if(fa[x]==x){
        return x;
    }else{
        return fa[x] = find(fa[x]);
    }
}
void kruskal(){
    int sum = 0;
    sort(a+1,a+1+n,cmp);//排序
    for(int i = 1; i<=n; i++){
        int x = find(a[i].x);
        int y = find(a[i].y);
        if(x!=y && cnt[a[i].x]<1){//满足条件
            fa[x] = y;
            cnt[a[i].x]++;//更新出边数量
            ans+=a[i].z;//加上权值
            sum++;
            if(sum==n-1){//已经是一条链了
                break;
            }
        }
    }
}
int main(){
    cin>>n;
    for(int i = 1; i<=n; i++){
        fa[i] = i;//初始化
        int x,y;
        cin>>x>>y;
        a[i] = node{i,x,y};//建边
    }
    kruskal();//最大生成树
    cout<<ans;//输出答案
    return 0;
}

总结

这题坑点不多,思路也不难想,总体不难,但坑点的最后一条如果中了,很难发现,会得27分,我就是找了很久加上重新证明和举反例才发现的,所以要细心呀。

posted @ 2026-08-22 22:57  xmgene  阅读(13)  评论(0)    收藏  举报