题解:洛谷 P9010 Leaders

【题目来源】

洛谷:P9010 [USACO23JAN] Leaders B - 洛谷

【题目描述】

农夫约翰有 \(N\) 头牛 \((2 \le N \le 10^5)\)。每头牛的品种要么是 Guernsey,要么是 Holstein。通常情况下,牛站成一排,按顺序编号为 \(1 \cdots N\)

在一天的过程中,每头牛都会写下一个牛的列表。具体来说,第 \(i\) 头牛的列表包含从她自己(第 \(i\) 头牛)开始到包括第 \(E_i(i \le E_i \le N)\) 头牛的范围。

FJ 最近发现,每种牛的品种都有一个独特的领导者。FJ 不知道领导者是谁,但他知道每个领导者的列表必须包括其品种的所有牛,或者其他品种的领导者(或两者)。

帮助 FJ 计算可能成为领导者的牛对数。保证至少有一对可能的领导者。

【输入】

第一行包含 \(N\)

第二行包含一个长度为 \(N\) 的字符串,其中第 \(i\) 个字符表示第 \(i\) 头牛的品种(G 表示 Guernsey,H 表示 Holstein)。保证至少有一个 Guernsey 和一个 Holstein。

第三行包含 \(E_1 \cdots E_N\)

【输出】

输出可能的领导者对数。

【输入样例】

4
GHHG
2 4 3 4

【输出样例】

1

【核心思想】

  1. 问题分析:给定 \(N\) 头牛排成一排,每头牛 \(i\) 的列表覆盖区间 \([i, E_i]\)。每头牛有品种 G 或 H。定义一个牛为“领导者”,当且仅当它的列表包含该品种所有牛,或包含另一品种的领导者(包含自身品种所有牛是被包含于另一品种领导者列表的充分条件)。求有多少对 \((i, j)\) 使得两头牛(品种不同)都能成为领导者。本质是区间覆盖 + 逻辑推导的组合计数问题,关键在于利用区间包含关系和品种分布的位置信息直接判定。

  2. 算法选择

    • 模拟 + 贪心推导:不需要遍历所有牛对,而是利用品种分布的极值位置(首尾出现位置)简化条件
    • 分类讨论思想:将牛的品种分为“首品种 A”和“另一种 B”,由于领导者的定义具有传递性,可推导出满足条件的候选牛集非常有限,从而直接计数
    • 区间包含判定:用 \(E_i\) 与品种首尾位置比较,快速判断列表是否覆盖全部同品种牛
  3. 关键步骤(基于课堂笔记的精简最优解法):

    • 预处理:读入 \(N\),品种字符串 \(s\)(调整为 1-based),\(E[1..N]\)
    • 确定品种分布边界
      • \(s[1]\) 的品种为 \(A\)(第一头牛的品种),另一品种为 \(B\)
      • 找出 \(A\) 品种的最后一头位置 \(lastA\)(遍历取最后出现的 \(A\)
      • 找出 \(B\) 品种的第一头位置 \(firstB\)(遍历找到第一个非 \(A\) 的位置)
    • 推导候选领导者范围:根据题意,只有 \(A\) 品种中位置在 \([1, firstB)\) 的牛(即 \(B\) 品种第一头之前的 \(A\) 牛)才可能成为领导者,因为若 \(A\) 牛位置 \(i \ge firstB\),其区间起点已在 \(B\) 品种中,无法覆盖 \(A\) 品种的第一头牛(位置 \(1\)),故不可能包含所有 \(A\)
    • 计数 \(A\) 品种的领导者数量(遍历 \(i\)\(1\)\(firstB-1\)):
      • \(i == 1\)\(E[1] \ge lastA\)(第一头 \(A\) 牛覆盖所有 \(A\) 牛),则该牛是领导者
      • \(E[i] \ge firstB\)(该 \(A\) 牛的列表覆盖到 \(B\) 品种第一头,即覆盖了 \(B\) 的领导者,因为 \(B\) 的领导者必然在 \(B\) 品种范围内且位置 \(\ge firstB\)),则该牛也是领导者
      • 两种情况满足任一即计数加 \(1\)
    • 统计答案:上述计数即为全部可能的领导者对数(因为 \(B\) 品种的领导者唯一且必为 \(B\) 品种第一头牛,且 \(A\) 的每个领导者与 \(B\) 的唯一领导者均可组成一对),直接输出计数
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N)\),仅需若干次线性扫描
    • 空间复杂度:\(O(N)\),存储 \(E\) 数组和品种字符串
  5. 模拟 + 分类讨论的核心思想

    • 位置边界简化:利用“品种首尾位置”作为关键边界,将区间覆盖判定从 \(O(N^2)\) 降为 \(O(1)\)
    • 领导者唯一性:在每种品种中,能覆盖该品种所有牛的领导者最多一头(必须是该品种最靠前的牛且 \(E\) 够大),这大幅缩小了候选集合
    • 包含关系的非对称性:通过将品种分为 \(A\)\(B\),并只枚举 \(A\) 种的候选者,利用对称逻辑避免重复计数
    • 隐含条件挖掘:只有位于另一种品种第一头之前的牛,才可能覆盖自身品种的全部牛,这是关键的剪枝条件
    • 适用于具有“区间覆盖”和“类别包含”约束的配对计数问题,核心是找出决定性的少数关键位置

【解题思路】

【算法标签】

普及- #模拟

【代码详解】

#include <bits/stdc++.h>
using namespace std;
int n, e[200005], firstH=0, lastH=0, firstG=0, lastG=0, ansG[200005]={0}, ansH[200005]={0}, cntG=0, cntH=0;  // ans1记录该头牛是否包含所有牛
int leaderG=0, leaderH=0;
string s;
int main()
{
    cin >> n;  // 输入n
    cin >> s;  // 输入字符串(方便查找)
    s = " " + s;  // 因为起始位置为1,所以在字符串前面加个空格
    for (int i=1; i<=n; i++) {  // 遍历n头奶牛
        cin >> e[i];  // 记录每头奶牛的名单
    }
    firstG = s.find('G');  // 获取第一头G奶牛的位置
    lastG = s.rfind('G');  // 获取最后一头G奶牛的位置
    firstH = s.find('H');  // 获取第一头H奶牛的位置
    lastH = s.rfind('H');  // 获取最后一头H奶牛的位置
    for (int i=1; i<=n; i++) {  // 遍历所有奶牛,找到包含所有同种品种奶牛的位置
        if (s[i]=='G') {  // 找G牛
            if (i<=firstG && e[i]>=lastG) {  // 如果包含所有同种品种的奶牛
                ansG[i]=1;  // 标记为1
                leaderG = i;  // 并且记录位置(G中有且只有一头头牛)
            }
        }
        if (s[i]=='H') {  // 找H牛
            if (i<=firstH && e[i]>=lastH) {  // 如果包含所有同品种的奶牛
                ansH[i]=1;  // 标记为1
                leaderH = i; // 并记录位置(H中也有且只有一头头牛)
            }
        }
    }
    for (int i=1; i<=n; i++) {  // 再次遍历n头奶牛
        if (s[i]=='G') {  // 查找满足条件:名单上记录另一品种奶牛的领导者
            if (i<=leaderH && e[i]>=leaderH) ansG[i]= 1;  // 满足条件的标记为1
        }
        if (s[i]=='H') {  // 同上
            if (i<=leaderG && e[i]>=leaderG) ansH[i]= 1;
        }
    }
    for (int i=1; i<=n; i++) {  // 最后将G牛和H牛种中标记为1的牛进行统计
        if (ansG[i]==1) cntG++;
        if (ansH[i]==1) cntH++;
    }
    cout << cntG*cntH << endl;  // 将两者统计结果相乘,得到方案数
    return 0;
}
// 学而思课堂笔记
#include <bits/stdc++.h>
using namespace std;
int n, a[100005], ans;
//第i头奶牛的领导范围:i~a[i];
string s;

int main()
{
    cin >> n >> s;
    s = " " + s;
    for (int i=1; i<=n; i++) cin >> a[i];
    int Alast, Bfirst;  // 我们将s[1]的奶牛的品种称为A,另一种称为B
    // A种奶牛的最后一头编号为Alast,B种奶牛的第一头编号为Bfirst
    for (int i=1; i<=n; i++) {  // 找最后一头A种奶牛的编号
        if (s[i]==s[1]) {
            Alast = i;
        }
    }
    for (int i=2; i<=n; i++) {  // 找第一头B种奶牛的编号
        if (s[i]!=s[1]) {
            Bfirst = i;
            break;
        }
    }
    // A种奶牛成为领导者的条件:
    // 1.领导所有A种奶牛:i==1 && a[i]>=Alast
    // 2.领导B种奶牛领导者:a[i]>=Bfirst
    for (int i=1; i< Bfirst; i++) {
        if (i==1 && a[i]>=Alast || a[i]>=Bfirst) {
            ans++;
        }
    }
    cout << ans << endl;

    return 0;
}

【运行结果】

4
GHHG
2 4 3 4
1
posted @ 2026-08-29 09:40  团爸讲算法  阅读(7)  评论(0)    收藏  举报