UVa 10654 The Uxuhul Voting System

题目分析

本题描述了一个古代文明的投票系统,我们需要根据每位祭司的偏好顺序,推算出最终三个议题的投票结果。这个问题的核心在于每位祭司都会基于后续祭司的最优选择来做出自己的最优决策,因此我们需要逆向推理 整个投票过程。

问题重述

  • 有三个议题,每个议题用一块双面石头表示(黑面 N / 白面 Y)。
  • 初始状态为 NNN(全否)。
  • mmm 位祭司按年龄从小到大依次投票。
  • 每位祭司必须翻转恰好一块石头,改变一个议题的当前结果。
  • 投票结束后,石头的最终朝上面决定三个议题的最终结果。
  • 每位祭司在投票前会公开自己对 888 种可能结果的偏好顺序(1 表示最喜欢,8 表示最不喜欢)。
  • 每位祭司都是完美理性的,会根据已知的其他祭司偏好,选择能使最终结果对自己最有利的翻转方式。
  • 给定 nnn 个测试用例,每个用例给出 mmm 和每位祭司的偏好顺序,要求输出最终的投票结果。

关键点

  1. 状态表示 :三个议题共有 23=82^3 = 823=8 种状态,可以用 000777 的整数表示,其中二进制位 000 表示第一个议题(最低位对应第一块石头),1 表示 N0 表示 Y(或者反过来,只要一致即可)。在代码中我们使用 outcomes 数组进行映射。
  2. 决策顺序 :祭司按年龄从小到大投票,但决策逻辑是逆向的。因为最后一位祭司投票后状态即为最终结果,而前面的祭司会预判后续祭司的选择。
  3. 最优子结构 :对于当前祭司,他可以根据下一个祭司在不同状态下的最终结果,选择对自己最有利的翻转方式。这符合动态规划的特性。

解题思路

逆向动态规划

dp[i][state]dp[i][state]dp[i][state] 表示从第 iii 位祭司(0-based)开始投票,当前状态为 statestatestate0−70 - 707)时,最终会达到的结果(用 0−70 - 707 表示,对应 888 种结果)。

  • 边界条件 :当所有祭司都投票完毕(即 i=mi = mi=m),最终状态就是当前状态,所以 dp[m][state]=statedp[m][state] = statedp[m][state]=state
  • 状态转移 :对于第 iii 位祭司,当前状态为 statestatestate,他可以翻转三块石头中的任意一块。翻转第 kkk 块石头(k=0,1,2k = 0,1,2k=0,1,2)会得到新状态 nextState=state⊕(1≪k)nextState = state \oplus (1 \ll k)nextState=state(1k)(异或操作翻转对应位)。然后,第 i+1i+1i+1 位祭司在该状态下的最终结果是 dp[i+1][nextState]dp[i+1][nextState]dp[i+1][nextState]。第 iii 位祭司会从这三种可能的 nextStatenextStatenextState 中,选择使自己的偏好值最小(即最喜欢)的那个最终结果作为自己的选择,并将该结果记录在 dp[i][state]dp[i][state]dp[i][state] 中。

转移方程

dp[i][state]=arg⁡min⁡k∈{0,1,2}preferences[i][ dp[i+1][ state⊕(1≪k) ] ] dp[i][state] = \arg\min_{k \in \{0,1,2\}} preferences[i][\,dp[i+1][\,state \oplus (1 \ll k)\,]\,] dp[i][state]=argk{0,1,2}minpreferences[i][dp[i+1][state(1k)]]

其中 arg⁡min⁡\arg\minargmin 返回的是对应偏好值最小的那个最终结果编号。

计算步骤

  1. 读取测试用例数 nnn
  2. 对每个用例:
    • 读取祭司人数 mmm
    • 读取 mmm 行偏好顺序,每行 8 个整数。
    • 初始化 dp[m][0..7]dp[m][0..7]dp[m][0..7] 为对应状态值。
    • i=m−1i = m-1i=m1000 逆序计算 dp[i][state]dp[i][state]dp[i][state]
    • 最终 dp[0][0]dp[0][0]dp[0][0] 就是从第一位祭司、初始状态 NNN(即 000)开始最终会达到的结果编号。
    • 通过 outcomes 数组转换为字符串输出。

复杂度分析

  • 状态数:m×8m \times 8m×8
  • 每个状态需要尝试 333 种翻转。
  • 总时间复杂度:O(n×m×8×3)=O(24nm)O(n \times m \times 8 \times 3) = O(24nm)O(n×m×8×3)=O(24nm),在 n,m<100n, m < 100n,m<100 时完全可行。
  • 空间复杂度:O(m×8)O(m \times 8)O(m×8)

代码实现

// The Uxuhul Voting System
// UVa ID: 10654
// Verdict: Accepted
// Submission Date: 2025-12-14
// UVa Run Time: 0.000s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net

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

// 8种可能的结果字符串,索引0~7对应状态0~7
const string outcomes[] = {"NNN", "NNY", "NYN", "NYY", "YNN", "YNY", "YYN", "YYY"};

int main() {
    int n;
    cin >> n;
    while (n--) {
        int m;
        cin >> m;
        vector<vector<int>> preferences(m, vector<int>(8));
        // 读取每位祭司的偏好顺序
        for (int i = 0; i < m; i++) for (int j = 0; j < 8; j++) cin >> preferences[i][j];
        
        // dp[i][state] 表示从第i位祭司开始,当前状态为state时,最终结果的编号
        vector<vector<int>> dp(m + 1, vector<int>(8, 0));
        // 边界:最后一位祭司之后,状态即最终结果
        for (int state = 0; state < 8; state++) dp[m][state] = state;
        
        // 逆序DP,从最后一位祭司往前推
        for (int i = m - 1; i >= 0; i--) {
            for (int state = 0; state < 8; state++) {
                int bestPreference = INT_MAX;  // 偏好值越小越好
                int bestOutcome = -1;
                // 尝试翻转三块石头中的一块
                for (int flip = 0; flip < 3; flip++) {
                    int nextState = state ^ (1 << flip);  // 翻转第flip位
                    int finalOutcome = dp[i + 1][nextState];  // 后续祭司的最终结果
                    // 选择对自己最有利的(偏好值最小)
                    if (preferences[i][finalOutcome] < bestPreference) {
                        bestPreference = preferences[i][finalOutcome];
                        bestOutcome = finalOutcome;
                    }
                }
                dp[i][state] = bestOutcome;
            }
        }
        
        // 输出从第一位祭司、初始状态0(NNN)开始的最终结果
        cout << outcomes[dp[0][0]] << endl;
    }
    return 0;
}

总结

本题是一个典型的逆向动态规划问题,关键在于理解每位祭司的决策依赖于后续祭司的最优选择。通过从最后一位祭司开始向前递推,我们可以确定在任意状态下最终会达到的结果。最终答案即为 dp[0][0]dp[0][0]dp[0][0] 对应的结果字符串。

这种“逆推最优决策”的思想在博弈论和动态规划中非常常见,本题提供了一个很好的应用实例。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值