AcWing 1738. 蹄球

273 阅读3分钟

目录:算法日记

题目来源:1738. 蹄球 - AcWing题库

题目描述

为了准备即将到来的蹄球锦标赛,Farmer John 正在训练他的 NN 头奶牛(方便起见,编号为 1N1…N)进行传球。

这些奶牛在牛棚一侧沿直线排列,第 ii 号奶牛位于距离牛棚 xix_i 的地方。

每头奶牛都在不同的位置上。

在训练开始的时候,Farmer John 会将若干个球传给不同的奶牛。

当第 ii 号奶牛接到球时,无论是从 Farmer John 或是从另一头奶牛传来的,她会将球传给最近的奶牛(如果有多头奶牛与她距离相同,她会将球传给这些奶牛中最左边的那头奶牛。)。

为了使所有奶牛都有机会练习到传球,Farmer John 想要确保每头奶牛都持球至少一次。

帮助他求出为了达到这一目的他开始时至少要传出的球的数量。

假设他在开始的时候能将球传给最适当的一组奶牛。

输入格式

输入的第一行包含 NN

第二行包含 NN 个用空格分隔的整数,其中第 ii 个整数为 xix_i

输出格式

输出 Farmer John 开始的时候最少需要传出的球的数量,使得所有奶牛至少持球一次。

数据范围

  • 1N1001≤N≤100
  • 1xi10001≤x_i≤1000

输入样例

5
7 1 3 11 4

输出样例

2

算法思路

核心规则为,奶牛会将球传给最近的奶牛,如果距离相等则左传。在题目中,球是一传一的,因此对于每一个传出的球,一定存在一个回传的位置T,将球回传给上一个位置P,使得在T与P之前形成一个环。对应产生两种情况,如下图所示。

未命名文件(6).png

未命名文件(6.png

由上图可以看出,对于图中的每个顶点,传给谁是唯一确定的,而谁传球给我多出一种回传的情况(入度为22)和Farmer传球给我情况(入度为00),因此对于每个顶点,入度2≤2,出度=1=1。则对于图中每块(可能存在多块)的一般情况,如下图所示。

未命名文件(.png

要确保每头奶牛都持球至少一次,就需要按有向边的方向遍历到所有顶点。对于上图的一般情况,则农夫仅需要向入度为0的顶点传球,就能保证该块内的所有顶点都可以被遍历到。

在此题中可能存在一种特殊块不能被一般情况覆盖,如下图所示。该块仅存在环,不存在入度为00的顶点,但仍需传入一个球。对于这种情况,需要判断是否是环(判断顶点A的后继的后继是否是A)以及环上两个顶点的入度是否为11。为了方便计算,如果存在该情况,每个顶点则需传入0.50.5个球(为避免小数存在精度问题,在统计时可×2×2当作整数处理)。

未命名文件.png

AC代码

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 110;
const int INF = 1e9;
int nums[N];
int in_node[N]; //入度
int next_node[N]; //后继
int n;
int main() {
    cin>>n;
    for(int i = 1; i <= n; ++i) cin>>nums[i];
    nums[0] = -INF;
    nums[n + 1] = INF;
    sort(nums + 1, nums + 1 + n);
    for(int i = 1; i <= n; ++i) {
        if(nums[i] - nums[i - 1] <= nums[i + 1] - nums[i]) {
            next_node[i] = i - 1;
            in_node[i - 1] += 1;
        } else {
            next_node[i] = i + 1;
            in_node[i + 1] += 1;
        }
    }
    int res = 0;
    for(int i = 1; i <= n; ++i) {
        if(in_node[i] == 0) res += 2;
        //是否有环,环上两点入度是否为1
        else if(next_node[next_node[i]] == i && in_node[i] == 1 && in_node[next_node[i]] == 1) res += 1; 
    }
    cout<<res / 2<<endl;
    return 0;
}