题解:洛谷 P10131 Majority Opinion

【题目来源】

洛谷:P10131 [USACO24JAN] Majority Opinion B - 洛谷

【题目描述】

Farmer John 有一项重要的任务——弄清楚要为他的奶牛们购买什么类型的干草。

Farmer John 的 \(N\) 头奶牛(\(2\le N\le 10^5\))编号为 \(1\)\(N\),每头奶牛喜欢恰好一种类型的干草 \(h_i\)\(1\le h_i\le N\))。他希望他的所有奶牛都喜欢同一种干草。

为了实现这一目标,Farmer John 可以主持焦点小组访谈。一次焦点小组访谈为让编号从 \(i\)\(j\) 的连续范围内的所有奶牛聚集在一起参加一次访谈。如果有一种干草是小组中超过一半的奶牛喜欢的,则此次焦点小组访谈结束后,所有奶牛最终都会喜欢这种干草。如果不存在这种类型的干草,那么奶牛们不会改变她们喜欢的干草类型。例如,在由 \(16\) 头奶牛组成的焦点小组访谈中,需要有其中 \(9\) 头或更多的奶牛具有相同的干草喜好,才能使其余奶牛改变其喜好以与之一致。

Farmer John 想知道哪些类型的干草有可能变为同时受到所有奶牛的喜爱。他一次只能主持一个焦点小组访谈,但为了使所有奶牛都喜欢同一类型的干草,他可以根据需要任意多次地主持焦点小组访谈。

【输入】

输入的第一行包含一个整数 \(T\),为独立的测试用例的数量(\(1\le T\le 10\))。

每一个测试用例的第一行包含 \(N\)

第二行包含 \(N\) 个整数,为奶牛们喜爱的干草类型 \(h_i\)

输入保证所有测试用例的 \(N\) 之和不超过 \(2\cdot 10^5\)

【输出】

输出 \(T\) 行,对于每个测试用例输出一行。

如果可能使所有奶牛同时喜欢同一种干草,则以升序输出所有可能的此类干草的类型,否则输出 \(-1\)。在同一行内输出一列整数时,相邻的数用空格分隔,并确保行末没有多余空格。

【输入样例】

5
5
1 2 2 2 3
6
1 2 3 1 2 3
6
1 1 1 2 2 2
3
3 2 3
2
2 1

【输出样例】

2
-1
1 2
3
-1

【核心思想】

  1. 问题分析:给定一个长度为 \(N\) 的喜好数组 \(h_i\)(值域 \(1..N\)),每次操作可以选择一个连续区间 \([l, r]\),若区间内存在某种喜好值出现次数严格超过区间长度的一半,则区间内所有奶牛都会变成该喜好值。可以反复操作任意次。问最终能否使所有奶牛喜好统一,并输出所有可能统一的喜好值。关键观察:由于每次操作只改变一个区间,且需要严格多数,那么任何能够通过一系列操作传播开的喜好值,必然在原始数组中存在一个长度为 2 或 3 的“种子”——即存在相邻两个相同,或间隔一个相同(形如 x y x)。因为一个值要在一个区间内成为多数,该区间内至少有两个该值相邻或间隔不超过 1 个位置(否则无法形成多数)。一旦这样的种子出现,就可以通过反复操作将该值向两侧扩展,最终覆盖整个数组。因此,只需检查所有长度为 2 和 3 的子串中是否有重复元素,并标记这些元素为可能答案。

  2. 算法选择

    • 扫描 + 标记:遍历数组,检查所有长度为 2 的相邻对 \((h_i, h_{i+1})\) 和长度为 3 的连续三元组 \((h_i, h_{i+1}, h_{i+2})\)。若其中存在重复元素(即 \(h_i = h_{i+1}\)\(h_i = h_{i+2}\)\(h_{i+1} = h_{i+2}\)),则将该重复值标记为“可行”。最后输出所有被标记的值(升序),若没有则输出 -1
  3. 关键步骤

    • 读入数据:读取 \(T\),对每个测试用例读入 \(N\) 和数组 \(h[1..N]\)
    • 初始化标记数组:用布尔数组 vis[1..N] 记录哪些喜好值可行。
    • 扫描:对 \(i\) 从 1 到 \(N-2\)
      • \(h_i == h_{i+1}\),标记 vis[h_i] = true
      • \(h_i == h_{i+2}\),标记 vis[h_i] = true
      • \(h_{i+1} == h_{i+2}\),标记 vis[h_{i+1}] = true
    • 特判 \(N=2\):单独处理,若两数相同则输出该数,否则 -1
    • 输出:按升序收集所有 vis 为真的值输出,若无则输出 -1
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N)\),每个测试用例线性扫描。
    • 空间复杂度:\(O(N)\),用于标记数组和存储输入。
  5. 贪心 + 局部传播的核心思想

    • 传播的可达性:一个值 \(x\) 能够最终统一整个数组,当且仅当在原始数组中存在一对相邻的 \(x\)(即 x x)或一个长度为 3 的 x y x 模式。因为:
      • 若存在 x x,以这两个 \(x\) 为中心,可以逐步向左或向右扩展区间,每次扩展一个位置,只要当前区间内 \(x\) 的数量超过一半,最终覆盖整个数组。
      • 若存在 x y x,同理,这两个 \(x\) 也可作为种子。
      • 反之,若数组中没有任何长度为 2 或 3 的模式含有 \(x\),则任何包含 \(x\) 的区间中 \(x\) 的数量都不可能超过一半(因为 \(x\) 之间至少相隔两个其他值),因此 \(x\) 无法成为多数,也无法扩展。
    • 充分性:一旦选定种子,通过反复选择包含种子和相邻位置的区间,可以逐步“感染”整个数组,最终使所有值变为 \(x\)
    • 适用场景:适用于“多数传播”模型,其中某值在区间内达到严格多数即可同化整个区间。核心是识别能形成多数的最小局部模式。

【算法标签】

普及- #数学

【代码详解】

#include <bits/stdc++.h>
using namespace std;

int T;                  // 测试用例的数量
int n;                  // 每个测试用例的数字个数
int a[100005];          // 存储输入的数字序列
int b[100005];          // 标记数组,记录哪些数字满足条件

int main()
{
    // 输入测试用例数量
    cin >> T;

    // 处理每个测试用例
    for (int t = 1; t <= T; t++)
    {
        // 清空数组,重置为0
        memset(a, 0, sizeof(a));
        memset(b, 0, sizeof(b));

        // 输入数字个数
        cin >> n;

        // 输入数字序列
        for (int i = 1; i <= n; i++)
        {
            cin >> a[i];
        }

        // 特殊情况处理:当n=2时
        if (n == 2 && a[1] != a[2])
        {
            // 只有两个数字且不相等,无解
            cout << "-1" << endl;
            continue;  // 跳过后续处理
        }
        else if (n == 2 && a[1] == a[2])
        {
            // 只有两个数字且相等,直接输出该数字
            cout << a[1] << endl;
            continue;  // 跳过后续处理
        }

        // 遍历序列,检查满足条件的数字
        for (int i = 1; i < n - 1; i++)
        {
            // 检查连续三个数字的相等关系

            // 情况1:相邻两个数字相等
            if (a[i] == a[i + 1])
            {
                b[a[i]] = 1;  // 标记该数字满足条件
            }

            // 情况2:间隔一个数字相等(i和i+2)
            if (a[i] == a[i + 2])
            {
                b[a[i]] = 1;  // 标记该数字满足条件
            }

            // 情况3:后两个数字相等(i+1和i+2)
            if (a[i + 1] == a[i + 2])
            {
                b[a[i + 1]] = 1;  // 标记该数字满足条件
            }
        }

        int flag = 0;      // 标记是否找到满足条件的数字
        string s = "";     // 用于构建输出字符串

        // 遍历所有可能的数字(1到100000)
        for (int i = 1; i <= 100000; i++)
        {
            // 如果当前数字被标记为满足条件
            if (b[i] != 0)
            {
                // 将数字添加到输出字符串
                s += to_string(i);
                s += " ";
                flag = 1;  // 设置找到标志
            }
        }

        // 移除最后一个空格
        s = s.substr(0, s.length() - 1);

        // 根据是否找到结果输出
        if (flag)
        {
            cout << s << endl;  // 输出所有满足条件的数字
        }
        else
        {
            cout << "-1" << endl;  // 无解情况
        }
    }

    return 0;
}

【运行结果】

5
5
1 2 2 2 3
2
6
1 2 3 1 2 3
-1
6
1 1 1 2 2 2
1 2
3
3 2 3
3
2
2 1
-1
posted @ 2026-08-28 15:53  团爸讲算法  阅读(1)  评论(0)    收藏  举报