首页 > 其他分享 >[PKUCPC2023] J. Hat Puzzle 题解

[PKUCPC2023] J. Hat Puzzle 题解

时间:2023-05-28 22:01:03浏览次数:48  
标签:帽子 报告 题解 Puzzle mn 信息 i32 2n Hat

题目链接:http://poj.openjudge.cn/campus2023/J/

很荣幸参与了命题。

题解的 ppt 版本在这儿:https://disk.pku.edu.cn:443/link/E4B484E7F3C58A45E9E4FB19C731BF4E.

贴一下 md 版题解,要比 ppt 版本的简略一些:

每个人能够推断出自己帽子颜色的信息,仅有两类:前面的人的帽子情况,以及其他人的报告情况。注意,这里的报告情况不仅包括每个人是否报告,也包括了每个人在哪一轮做出的报告。

梳理完毕这些信息后,我们先证明两个引理。

Lemma 1:对于进行中的任何状态,如果 \(i\) 确定了自己最终是否报告,那么 \(i + 1\) 也确定了自己最终是否报告。此处“确定”是指,\(i\) 已经报告,或者从当前到无穷的时间上,\(i\) 都不会报告。

Proof 1:设 \(i \lt j\),由于 \(j\) 掌握的信息严格比 \(i\) 多,故虽然 \(j\) 可能无法报告,但如果 \(i\) 可以报告,则 \(j\) 一定可以知道 \(i\) 可以报告。故而说明 \(i\) 的报告对 \(j\) 做出判断没有影响。也就是说前面的人的报告情况不影响后面人的报告情况,即证明了引理 1。

Lemma 2:对于每一轮的报告,宣布报告的人一定是还未确定最终是否报告的人中的一段后缀。

Proof 2:只需证明,如果 \(i + 1\) 无法报告,则 \(i\) 也无法报告。使用反证法,不妨设 \(i\) 报告自己的帽子为白色,由于 \(i + 1\) 无法确定,则 \(i + 1\) 可能是黑色。那么从 \(i\) 的视角来看,他从 \(i + 2\) 得到的后面的报告信息,以及自己观察到的前面帽子信息中,该组合信息与 \(i\) 是黑色 \(i + 1\) 是白色完全对称(相当于 \(i\) 和 \(i + 1\) 是一个黑箱,两人交换帽子颜色对黑箱外没有影响),故该情况同样是有可能的,与 \(i\) 能确定自己的帽子颜色矛盾。故引理 2 得证。

上述两个引理告诉我们,报告不必进行无限时间,只需要考虑从后往前的若干轮,这个轮数不会超过 \(n\) 轮。这样的结果与原题目是等价的。

简单化归题目后,难点在于如何具象化这个“后面的人的报告情况”,也即,后面的报告提供了怎样有用的信息。

直观上的想法是,这个信息可以表示为 \((x, y)\) 的形式,表示剩余帽子中两种颜色的个数。由于大家都不知道 \(0\) 号帽子颜色,因此帽子的颜色是对称的,也即 \((x, y)\) 可能表示有 \(x\) 顶白帽 \(y\) 顶黑帽,也可能表示 \(x\) 顶黑帽 \(y\) 顶白帽。以下我们以具体例子算法过程,也即如何维护每个人携带的信息。

为了方便,我们假定一个虚拟的 \(2n\) 号,携带了信息 \((n, n - 1)\),表示 \(1\) 号到 \(2n - 1\) 号当中帽子颜色个数。在最开始时,\(2n - 1\) 号获得了该信息,它根据自己所见前方帽子的颜色情况与这个信息进行推断。

  • 若白色 \(n\),黑色 \(n - 2\),则知道自己用掉了 \(n - 1\) 中的一个东西,也就是自己帽子颜色与 \(n - 2\) 这个值相等,故而判断出自己是黑色。
  • 若黑色 \(n\),白色 \(n - 2\),同理,判断出自己是白色。
  • 若白色 \(n - 1\),黑色 \(n - 1\),虽然知道自己用掉的是 \(n\) 中的一个东西,但是无法判断出用的是哪一个 \(n - 1\),故而无法判断。

接下来,\(2n - 1\) 号是否报告的结果,将一个新的信息携带到自己身上:

  • 若 \(2n - 1\) 报告,说明结果要么是白色 \(n\),黑色 \(n - 2\);要么是黑色 \(n\),白色 \(n - 2\)。故而携带的新信息是 \((n, n - 2)\)。事实上,可以归纳得到,此后任何情况都具有这样的白色黑色的对称性。
  • 若 \(2n - 1\) 没有报告,说明结果一定是白黑各 \(n - 1\),故而携带信息 \((n - 1, n - 1)\)。

此后,\(2n - 2\) 号利用他所知道的 \(2n - 1\) 号和 \(2n\) 号携带的信息,做类似的推理。通过计算可知,\(2n - 2\) 号一定报告。稍微有一些区别的是,如果他观察到前面的情况是 \((n - 1, n - 2)\),那么他会要根据 \(2n - 1\) 号是否报告,也就是在 \(2n - 1\) 号身上携带的信息,才能做出判断,也就是说他会在 \(2n - 1\) 号的下一轮做出报告;但如果他观察到前面的情况是 \((n, n - 3)\),那么他可以直接从 \(2n\) 推理过来,也就是说他会和 \(2n - 1\) 号在同一轮就做出了报告。虽然在前面的人看来,他一定做出了报告,但是由于报告时间的不同,他携带的信息要么是 \((n - 1, n - 2)\),要么是 \((n, n - 3)\),是有明显差异的。

这也给了我们一个启发,每个人计算自己能否报告帽子颜色及携带的信息时,需要从最后方的可以做出推断的人处获得。直觉上来看,跳过的一段区间相当于这一轮报告的区间,根据引理 2,该方法总是严格的。

于是,我们以此类推,从 \(2n - 1\) 号到 \(1\) 号逐个递推,每次判断前方帽子情况能不能由信息唯一推得,并根据自己及后方的人的报告情况计算自己将要携带什么信息即可。

朴素的实现可以做到时间复杂度 \(O(n ^ 4)\),即可通过本题。

然而这并不是重点。这个算法可以用来证明本题结论:某个人能够确定自己的帽子颜色,当且仅当他看到的帽子颜色数量不相等。

  • 必要性。必要性显然,若帽子颜色相同,则所有帽子反色后也是一种合法方案,故一定不能报告。
  • 充分性。我们只需要证明一个更强的结论:每一个人携带的信息有且仅有一个信息对。考虑第二数学归纳法,对于 \(2n\) 和 \(2n-1\) 显然满足条件,对于前面的某个 \(k\),如果他推得帽子颜色,则该轮报告的一定是一段颜色相等的后缀,那么可以唯一确定 \(k\) 的信息对;如果他无法推得帽子颜色,那么他看到的颜色一定相同(归纳可得),于是也只有一个信息对,于是证明了该结论。

综上,我们用一次 for 循环,\(O(n)\) 扫过去即可。

\(O(n ^ 4)\) 代码:

#include <algorithm>
#include <iostream>
#include <utility>
#include <vector>

using std::cin, std::cout;
using std::endl;
using std::pair;
using std::make_pair;
using std::swap;

using i32 = int;

template <typename T>
using vec = std::vector<T>;

void read(i32 &n, vec<i32> &a) {
    cin >> n;
    a.resize(2 * n);
    for (i32 i = 0; i < 2 * n; i += 1) { cin >> a[i]; }
}

bool can_derive(i32 mn, i32 mx, const vec< pair<i32, i32> > &g) {
    for (auto p: g) {
        if (mn <= p.first && mx <= p.second) {
            return true;
        }
    }
    return false;
}

bool can_infer(i32 mn, i32 mx, const vec< pair<i32, i32> > &g) {
    if (mn == mx) { return false; }
    bool possible_mn = false, possible_mx = false;
    for (auto p: g) {
        if (p.first >= mn + 1 && p.second >= mx) {
            possible_mn = true;
        }
        if (p.first >= mn && p.second >= mx + 1) {
            possible_mx = true;
        }
    }
    if (possible_mn != possible_mx) { return true; }
    else { return false; }
}

void derive(i32 n, i32 sum, bool inferred, vec< vec< pair<i32, i32> > > &info, i32 frm, i32 tar) {
    info[tar].clear();
    for (i32 mn = 0; mn <= sum; mn += 1) {
        i32 mx = sum - mn;
        if (mx > n) { continue; }
        if (mn > mx) { break; }
        if (can_derive(mn, mx, info[frm]) == false) { continue; }
        if (can_infer(mn, mx, info[frm]) == inferred) {
            bool check_this_round = true;
            if (inferred == true) {
                for (i32 i = frm + 1; i <= 2 * n; i += 1) {
                    if (can_infer(mn, mx, info[i]) == true) {
                        check_this_round = false;
                    }
                }
            }
            if (check_this_round == true) { info[tar].emplace_back(mn, mx); }
        }
    }
}

i32 solve(i32 n, const vec<i32> &a) {
    vec< vec< pair<i32, i32> > > info(2 * n + 1);
    info[2 * n].emplace_back(n - 1, n);
    i32 ans = 0;
    for (i32 i = 2 * n - 1; i >= 1; i -= 1) {
        i32 count_black = 0;
        for (i32 j = 1; j < i; j += 1) {
            count_black += a[j];
        }
        i32 count_white = i - 1 - count_black;
        if (count_black > count_white) {
            swap(count_black, count_white);
        }
        bool can_guess = false;
        for (i32 j = 2 * n; j > i; j -= 1) {
            if (can_infer(count_black, count_white, info[j]) == true) {
                ans += 1;
                can_guess = true;
                derive(n, i - 1, true, info, j, i);
                break;
            }
        }
        if (can_guess == false) {
            derive(n, i - 1, false, info, i + 1, i);
        }
    }
    return ans;
}

i32 main() {
    i32 n;
    vec<i32> a;
    read(n, a);
    i32 ans = solve(n, a);
    cout << ans << endl;
    return 0;
}

\(O(n)\) 代码:

#include <cstdio>

int main() {
    int n;
    int a[200];
    scanf("%d", &n);
    for (int i = 0; i < 2 * n; i ++) {
        scanf("%d", &a[i]);
    }
    int ans = 0;
    for (int i = 1, black = 0; i < 2 * n; i ++) {
        if (black * 2 != (i - 1)) {
            ans ++;
        }
        black += a[i];
    }
    printf("%d\n", ans);
    return 0;
}

标签:帽子,报告,题解,Puzzle,mn,信息,i32,2n,Hat
From: https://www.cnblogs.com/tweetuzki/p/17438960.html

相关文章

  • 文心一言 VS 讯飞星火 VS chatgpt (24)-- 算法导论4.2 6题
    六、用Strassen算法作为子进程来进行一个knn矩阵和一个nkn矩阵相乘,最快需要花费多长时间?对两个输入矩阵规模互换的情况,回答相同的问题。文心一言:Strassen算法是一种用于矩阵乘法的分治算法,它将原始的矩阵分解为较小的子矩阵,然后使用子矩阵相乘的结果来计算原始矩阵的乘积......
  • AtCoder Beginner Contest 303 题解 A - E
    A-SimilarString题目大意忽略0和o的差别以及1和l的差别比较两个字符串。解题思路可以硬求,直接写个超长的if判断一下。可以对两个字符串进行修改,0都换成o,1都换成l,然后直接比较两个字符串。ACCode硬求#include<iostream>#include<algorithm>#include<cstring>#i......
  • 文心一言 VS 讯飞星火 VS chatgpt (24)-- 算法导论4.2 6题
    六、用Strassen算法作为子进程来进行一个knn矩阵和一个nkn矩阵相乘,最快需要花费多长时间?对两个输入矩阵规模互换的情况,回答相同的问题。文心一言:Strassen算法是一种用于矩阵乘法的分治算法,它将原始的矩阵分解为较小的子矩阵,然后使用子矩阵相乘的结果来计算原始矩阵的乘积。对......
  • P9356 「SiR-1」Bracket 题解
    P9356「SiR-1」Bracket题解首先我们来先考虑一下如何计算一个给定的\(f(s[1,n])\)。一般括号序列的题目都是比较套路的将\(\texttt{(}\)赋值为\(1\),将\(\texttt{)}\)赋值为\(-1\),然后求一下前缀和记为\(sum_i\),那么一个括号序列是合法的当且仅当\(\foralli\in[1,n],......
  • comp2123 问题解答
    comp2123Assignment5s12023Problem1.Wewanttodesignadivideandconqueralgorithmforcomputingtheunionofacollectionofrectangles.Theinputrectanglesarealignedwiththeaxesandtheyareallstabbedbythey-axis.Eachrectangleisrepresen......
  • 如何使用chatgpt编写代码
    功能列举回答编程问题我想让你充当Stackoverflow的帖子。我将提出与编程有关的问题,你将回答答案是什么。我希望你只回答给定的答案,在没有足够的细节时写出解释。当我需要用英语告诉你一些事情时,我会把文字放在大括号里{XXXXXX}。写代码你现在是一个[程序语言]专家,请帮我用......
  • 【教程】手把手教你如何修改ChatGPT的密码
    申请OpenAI成功后,如何修改OpenAI的密码?OpenAI并没有内置账号安全管理的选项,因此它其实并没有绑定任何手机号的,手机号只是一道机器验证,邮箱也是。所以如果你用邮箱注册了OpenAI的话,后面是无法修改更换邮箱的。下面教你如何修改ChatGPT的密码,手机电脑端均可修改:第一步,打开OpenAI登陆......
  • LeetCode-Java题解 977. Squares of a Sorted Array
    题目地址:977.SquaresofaSortedArray解题思路:    又是一道双指针的题目,看见秒想到双指针(平方直接调用sort方法也行,但是这么写这题就没意思了)。但是,我一直在想,不增加空间消耗的情况下,如何进行排列,想了半天把自己绕进去了。开辟一个新数组,倒序放置就非常简单了。一定要利......
  • phpcms常见问题解答
    phpcms常见问题解答1.为什么phpcms首页幻灯片怎么显示不出来?答:需要设置文章的标题图片如果设置标题图片,则可以在首页以及栏目页以图片方式链接到文章。2.自定义phpcms的标签只能是全HTML?答:在自定义标签内容中可以插入html代码,也可以插入多个函数标签或者变量标签。插入函......
  • 通俗直观介绍ChatGPT背后的大语言模型理论知识
    “AI的iPhone时刻到来了”。非算法岗位的研发同学'被迫'学习AI,产品岗位的同学希望了解AI。但是,很多自媒体文章要么太严谨、科学,让非科班出身的同学读不懂;要么,写成了科幻文章,很多结论都没有充分的逻辑支撑,是‘滑坡推理’的产物。这篇文章从底层讲起,却不引入太多概念,特别是数......