计算机系程设期末考试部分题目
计算机系程设期末考试部分题目
P2608 逻辑岛真假话推理问题
题目描述
在「逻辑岛」上住着 \(N\) (\(N \leq 10\)) 个人,编号为 \(1 \sim N\)。每个人是如下两种类型之一:
- 诚实者:永远说真话;
- 说谎者:永远说假话。
某一天,他们排成一排,每个人各说了一句话。每句话都只讨论部分人,形式如下:
“在我提到的这些人(允许包括自己)中,说真话的人 至少 / 至多 / 恰好 有 \(k\) 个。”
你的任务是判断:根据所有人的发言,是否能够确定每个人究竟是在说真话还是在说假话。
你需要判断:
- 如果 不存在 任何一种真假话情况能让所有人的发言成立,则输出
Impossible; - 如果 存在多种 可能情况都能符合条件,则输出
Ambiguous; - 如果 恰好只有一种 符合条件的情况,则输出一个长度为 \(N\) 的字符串表示结果,其中:
- 如果第 \(i\) 个字符为
T,则表示第 \(i\) 个人说的是真话; - 如果第 \(i\) 个字符为
L,则表示第 \(i\) 个人说的是假话。
- 如果第 \(i\) 个字符为
示例:LTTT 表示 1 号说假话、2 号说真话、3 号说真话、4 号说真话。
输入描述
输入从标准输入读取。每组测试数据包含多个测试用例。
第一行包含一个正整数 \(T\) (\(T \leq 10\)),表示测试用例的个数。
第二行包含一个正整数 \(N\) (\(N \leq 10\)),表示岛上的人数(后续每个测试用例中岛上均有 \(N\) 人)。
随后给出 \(T\) 个测试用例。每个测试用例包含 \(N\) 行描述,第 \(i\) 行表示第 \(i\) 个人的发言,格式为:op k m x1 x2 ... xm
各字段由空格分隔,含义如下:
op为一个字符,表示比较类型,只可能为g、l、e之一:g表示“至少”(大于等于);l表示“至多”(小于等于);e表示“恰好”(等于)。
k为一个整数,表示比较值;m为一个整数,表示提到的人的数量。保证 \(0 \leq k \leq m \leq N\)。x1 x2 ... xm为 \(m\) 个整数,表示被提到的人的编号(互不重复),且满足 \(1 \leq x_j \leq N\)。
例如:g 2 3 1 3 5 表示:在 1 号、3 号、5 号中,至少有 2 个人在说真话。
输出描述
输出到标准输出。
共 \(T\) 行输出,每行一个字符串,对应该测例的答案。
- 如果不存在任何真假话情况,输出
Impossible; - 如果存在多种可能情况,输出
Ambiguous; - 如果可以唯一确定一种情况,输出一个长度为 \(N\) 的字符串,表示每个人说真话或说假话的情况。
测试样例
样例输入
3
3
g 2 2 1 2
e 0 2 1 2
g 1 1 2
e 1 1 1
g 0 2 2 3
g 3 3 1 2 3
l 0 3 1 2 3
l 2 3 1 2 3
e 0 2 2 3
样例输出
Impossible
Ambiguous
LTL
样例解释
第一个测例中:
- 1 号:在 1、2 号中,至少有 2 人说真话(即“都在说真话”)。
- 2 号:在 1、2 号中,恰好有 0 人说真话(即“没人在说真话”)。
- 3 号:在 2 号中,至少有 1 人说真话(即“2 号在说真话”)。
这三句话互相矛盾,没有任何一种情况可以同时满足所有条件,所以输出 Impossible。
第二个测例中:
- 1 号:在 1 号中,恰好有 1 人说真话(即“我自己在说真话”)。
- 2 号:在 2、3 号中,至少有 0 人说真话(恒真)。
- 3 号:在 1、2、3 号中,至少有 3 人说真话(即“都在说真话”)。
这里既可以让三个人全说真话(TTT),也可以让只有 1、2 号说真话、3 号说假话(TTL),两种情况都能满足所有条件,所以输出 Ambiguous。
第三个测例中:
- 1 号:在 1、2、3 号中,至多有 0 人说真话(即“没人在说真话”)。
- 2 号:在 1、2、3 号中,至多有 2 人说真话。
- 3 号:在 2、3 号中,恰好有 0 人说真话(即“2 和 3 都在说假话”)。
推理可知,3 号不可能在说真话,只能是说谎者;由此可以唯一推出 2 号在说真话、1 号在说假话,所以输出 LTL。
数据范围
- 对于 \(10\%\) 的数据,\(N = 1\)。
- 对于 \(20\%\) 的数据,\(N \leq 2\)。
- 对于 \(40\%\) 的数据,\(N \leq 3\)。
- 对于 \(60\%\) 的数据,\(N \leq 4\)。
- 对于 \(100\%\) 的数据,\(T, N \leq 10\)。
参考代码(C++)
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string.h>
using namespace std;
int state[10001] = {0};
char op[10001];
int val[10001];
int num[10001];
int x[10001][1001];
void writeState(int x){
memset(state, 0, sizeof(state));
int i = 1;
while(x){
state[i] = x % 2;
x /= 2;
i++;
}
}
void outputState(int n, int x){
int cnt = 0;
while(x){
int t = x % 2;
cnt++;
if(t) cout << "T";
else cout << "L";
x /= 2;
}
for(int i = 0; i < n - cnt; i++){
cout << "L";
}
}
int final[10001];
int main(){
int kk;
cin >> kk;
int n;
cin >> n;
for(int ll = 0; ll < kk; ll++){
for(int i = 1; i <= n; i++){
cin >> op[i] >> val[i] >> num[i];
for(int j = 1; j <= num[i]; j++){
cin >> x[i][j];
}
}
int ans = 0;
for(int i = 0; i < pow(2, n); i++){
writeState(i);
bool isCurrentValid = true;
for(int j = 1; j <= n && isCurrentValid; j++){
int temp = 0;
for(int k = 1; k <= num[j]; k++){
temp += state[x[j][k]];
}
if(op[j] == 'g'){
if(state[j] != (temp >= val[j])){
isCurrentValid = false;
continue;
}
} else if(op[j] == 'l'){
if(state[j] != (temp <= val[j])){
isCurrentValid = false;
continue;
}
} else {
if(state[j] != (temp == val[j])){
isCurrentValid = false;
continue;
}
}
}
if(isCurrentValid){
ans++;
final[ans] = i;
}
}
if(ans > 1){
cout << "Ambiguous" << endl;
} else if(ans < 1){
cout << "Impossible" << endl;
} else {
outputState(n, final[1]);
cout << endl;
}
}
return 0;
}
P2626 面板修复
题目描述
在一块老旧的显示面板上,像素的亮灭决定了一条信号是否能够通过。整块面板可以抽象为一个大小为 \(H \times W\) 的单色网格,其中:
- 亮起的像素(用字符
.表示)允许信号通过 - 熄灭的像素(用字符
#表示)会将信号阻断
信号从左上角 \((1,1)\) 开始注入,只能沿着网格向右或向下传播,最终目标是抵达右下角 \((H,W)\)。
为了修复面板,工程师可以使用"单点翻转"操作:选择面板上任意一个像素,将其状态翻转(亮变暗或暗变亮)。每次执行该操作消耗 1 单位成本。
在信号经过的路径上(包含起点和终点),所有像素必须处于亮起(.)状态。如果像素原来状态是熄灭的(#),则必须消耗成本将其翻转。
请你计算使信号从起点到达终点最少需要执行多少次翻转操作。
输入描述
第一行包含两个整数 \(H, W\) \((1 \leq H, W \leq 2000)\)。
接下来 \(H\) 行,每行 \(W\) 个字符(字符为 . 或 #),描述面板的初始状态。
输出描述
输出一行,一个整数,表示使信号从起点到达终点最少需要执行的翻转操作次数。
测试样例
样例输入 1
3 3
.#.
..#
#..
样例输出 1
``` text 2 ```样例解释 1
面板初始状态:
行1: . # .
行2: . . #
行3: # . .
一种最优方案:翻转 (1,2) 和 (3,1) 这两个像素,使得路径上的所有像素都是 .。
翻转后的面板:
行1: . . .
行2: . . #
行3: . . .
信号可以沿着路径 (1,1)→(2,1)→(2,2)→(3,2)→(3,3) 或 (1,1)→(1,2)→(2,2)→(3,2)→(3,3) 到达终点。
样例输入 2
2 2
#.
.#
样例解释 2
需要翻转起点 (1,1) 和终点 (2,2),总共 2 次操作。
解题思路
这是一个动态规划问题。定义 \(dp[i][j]\) 为从起点 \((1,1)\) 到达 \((i,j)\) 所需的最小翻转次数。
状态转移方程:
- 如果当前位置 \((i,j)\) 是
#,需要翻转,则 \(cost = 1\),否则 \(cost = 0\) - 对于第一行:\(dp[1][j] = dp[1][j-1] + cost\)
- 对于第一列:\(dp[i][1] = dp[i-1][1] + cost\)
- 对于其他位置:\(dp[i][j] = \min(dp[i-1][j], dp[i][j-1]) + cost\)
最终答案为 \(dp[H][W]\)。
参考代码(C++)
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 2005;
int matrix[MAXN][MAXN];
int dp[MAXN][MAXN];
int main() {
int H, W;
cin >> H >> W;
char temp;
for(int i = 1; i <= H; i++) {
for(int j = 1; j <= W; j++) {
cin >> temp;
// '.' 表示亮起,不需要翻转,cost=0
// '#' 表示熄灭,需要翻转,cost=1
matrix[i][j] = (temp == '#') ? 1 : 0;
}
}
// 初始化DP数组
memset(dp, 0, sizeof(dp));
// 起点
dp[1][1] = matrix[1][1];
// 动态规划
for(int i = 1; i <= H; i++) {
for(int j = 1; j <= W; j++) {
if(i == 1 && j == 1) continue; // 起点已初始化
if(i > 1 && j > 1) {
// 可以从上方或左方到达
dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + matrix[i][j];
}
else if(i > 1) {
// 只能从上方到达(第一列)
dp[i][j] = dp[i-1][j] + matrix[i][j];
}
else if(j > 1) {
// 只能从左方到达(第一行)
dp[i][j] = dp[i][j-1] + matrix[i][j];
}
}
}
cout << dp[H][W] << endl;
return 0;
}

浙公网安备 33010602011771号