模擬
介紹
正如名稱所說,模擬是資訊學學生最早接觸,也是難度跨度最大的知識點。簡單如《A+B 問題》《校門外的樹》開門見山,沒有任何鋪墊和掩飾;困難如《豬國殺》《亂西星上的空戰》同樣開門見山,但誰做誰頭疼。
因此,本文選擇了模擬作為《演算法與資料結構》的第一章。
引入
正如名字所表示的,模擬的核心思想就是“複製貼上”:根據題目提供的資訊,將該題目的解決過程模擬出來就能夠得到最終的結果。下面以《[CSP-S 2023] 密碼鎖》為例進行介紹:
小 Y 有一把五個撥圈的密碼鎖。如圖所示,每個撥圈上是從 \(0\) 到 \(9\) 的數字。每個撥圈都是從 \(0\) 到 \(9\) 的迴圈,即 \(9\) 撥動一個位置後可以變成 \(0\) 或 \(8\),
因為校園裡比較安全,小 Y 採用的鎖車方式是:從正確密碼開始,隨機轉動密碼鎖僅一次;每次都是以某個幅度僅轉動一個撥圈或者同時轉動兩個相鄰的撥圈。
當小 Y 選擇同時轉動兩個相鄰撥圈時,兩個撥圈轉動的幅度相同,即小 Y 可以將密碼鎖從 \(\tt{0\;0\;1\;1\;5}\) 轉成 \(\tt{1\;1\;1\;1\;5}\),但不會轉成 \(\tt{1\;2\;1\;1\;5}\)。
時間久了,小 Y 也擔心這麼鎖車的安全性,所以小 Y 記下了自己鎖車後密碼鎖的 \(n\) 個狀態,注意這 \(n\) 個狀態都不是正確密碼。
為了檢驗這麼鎖車的安全性,小 Y 有多少種可能的正確密碼,使得每個正確密碼都能夠按照他所採用的鎖車方式產生鎖車後密碼鎖的全部 \(n\) 個狀態。
不難發現題目中的密碼鎖只有 \(5\) 位數,所以可以直接對每一個可能的密碼 \(x\)(\(x \in \mathbb{Z}\) 且 \(00000 \le x \le 99999\))進行判斷。判斷時分別檢查 \(n\) 個狀態和可能密碼不同的位。若只有 \(1\) 位不同則顯然可行;若有相鄰 \(2\) 位不同,但兩位與狀態的差值固定(即撥動了相同的幅度)也是可行的。然後直接統計即可。
#include <bits/stdc++.h>
using namespace std;
int n;
int a[10][10];
int ans = 0;
int b[10];
bool cmp(int x) {
int cnt = 0, pos;
for (int i = 1; i <= 5; i++) {
if (b[i] != a[x][i]) {
cnt++;
pos = i;
}
if (cnt > 2) return true; // 不同的位太多
}
if (cnt == 0) return true; // 沒動
if (cnt == 1) return false; // 只撥動了一位
if (b[pos - 1] == a[x][pos - 1]) return true; // 撥動的兩位不相鄰
// 計算兩位的幅度
int f = (b[pos] + 10 - a[x][pos]) % 10;
int g = (b[pos - 1] + 10 - a[x][pos - 1]) % 10;
return f != g;
}
bool test() {
for (int i = 1; i <= n; i++) {
if (cmp(i)) {
return false;
}
}
return true;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= 5; j++) {
scanf("%d", &a[i][j]);
}
}
for (int i = 0; i <= 9; i++) {
b[1] = i;
for (int j = 0; j <= 9; j++) {
b[2] = j;
for (int k = 0; k <= 9; k++) {
b[3] = k;
for (int l = 0; l <= 9; l++) {
b[4] = l;
for (int m = 0; m <= 9; m++) {
b[5] = m;
if (test()) {
ans++; // 直接統計
}
}
}
}
}
}
printf("%d\n", ans);
return 0;
}
如你所見,程式碼簡單直白,不帶任何掩飾。
進階
難度升級,但也沒升多少。只要大膽開寫,小心除錯,一切皆有可能!
下面有請:《德州撲克牌》(Texas Hold'em)!
一副撲克牌除去大小王,你和對手分別有 \(2\) 張已知手牌,桌面上現在有 \(3\) 張已知公共牌和 \(2\) 張未知公共牌。
翻開 2 張未知公共牌,現在有 \(5\) 張公共牌,你和對手分別擁有 \(2\) 張手牌,共 \(9\) 張牌。你不可以選擇對手的手牌,對手也不可以選擇你的手牌。在你自己的手牌和公共牌中,選取 \(5\) 張組成牌型。對手同理。雙方牌型可以有重複的公共牌。
你和對手都要選擇最佳組合,問在兩張未知牌的所有可能情況中,你的獲勝機率是多少。平局不屬於獲勝。
牌型的比較方法參考下表(上小下大):
題目的本意就是讓我們計算機率。眾所周知,列舉每種可能的情況後,用可行方案數除以總方案數就是機率。所以直接列舉剩下的 \(2\) 張公共牌就行了。剩下的時間交給寫程式碼和除錯!
// 程式碼中的註釋充滿 “Chinglish” 和拼音(因為之前那臺機器不支援中文輸入法),先湊合著看吧。
#include <bits/stdc++.h>
using namespace std;
struct card {
int c, n;
/*
c:
0 -> Spades
1 -> Hearts
2 -> Diamonds
3 -> Clubs
n:
A -> 14 / 1
K -> 13
Q -> 12
J -> 11
T -> 10
9-2
*/
};
map<int, bool> vis;
bool operator == (card &x, card &y) {
return x.c == y.c && x.n == y.n;
}
card me[2], en[2], pu[9]; // me, enemy, public(last 2 for users)
card a[5] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, b[5] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, c[5] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // compare a and b. a->me, b->enemy; c for the best choice
int s1, s2, s3; // judge kind of card
/*
9 -> huang jia tong hua shun
8 -> tong hua shun
7 -> si tiao
6 -> mam tang hong
5 -> tong hua
4 -> shun zi
3 -> san tiao
2 -> liang dui
1 -> yi dui
0 -> gao pai
*/
bool ok(int i, int j, int k, int l) {
return !vis[i*20+ j] && !vis[k*20+l] && i * 20 + j != k * 20 + l;
}
bool cmp(card &x, card &y) {
return (x.n != y.n) ? (x.n > y.n) : (x.c < y.c);
}
bool cmpa() {
/*
return value:
1 -> win
0 -> lost / draw
*/
// diff kind
if (s1 != s3) return s1 > s3;
if (s1 == 9) return 0;
if (s1 == 8) {
return a[0].n > c[0].n;
}
if (s1 == 7) {
if (a[0].n != c[0].n) return a[0].n > c[0].n;
return a[4].n > c[4].n;
}
if (s1 == 6) {
if (a[0].n != c[0].n) return a[0].n > c[0].n;
return a[4].n > c[4].n;
}
if (s1 == 5) {
for (int i = 0; i < 5; i++) {
if (a[i].n != c[i].n) return a[i].n > c[i].n;
}
return 0;
}
if (s1 == 4) {
return a[0].n > c[0].n;
}
if (s1 == 3) {
if (a[0].n != c[0].n) return a[0].n > c[0].n;
if (a[3].n != c[3].n) return a[3].n > c[3].n;
return a[4].n > c[4].n;
}
if (s1 == 2) {
if (a[0].n != c[0].n) return a[0].n > c[0].n;
if (a[3].n != c[3].n) return a[3].n > c[3].n;
return a[4].n > c[4].n;
}
if (s1 == 1) {
if (a[0].n != c[0].n) return a[0].n > c[0].n;
for (int i = 2; i < 5; i++) {
if (a[i].n != c[i].n) return a[i].n > c[i].n;
}
return 0;
}
for (int i = 0; i < 5; i++) {
if (a[i].n != c[i].n) return a[i].n > c[i].n;
}
return 0; // default
}
bool cmpb() {
/*
return value:
1 -> win
0 -> lost / draw
*/
// diff kind
if (s2 != s3) return s2 > s3;
if (s2 == 9) return 0;
if (s2 == 8) {
return b[0].n > c[0].n;
}
if (s2 == 7) {
if (b[0].n != c[0].n) return b[0].n > c[0].n;
return b[4].n > c[4].n;
}
if (s2 == 6) {
if (b[0].n != c[0].n) return b[0].n > c[0].n;
return b[4].n > c[4].n;
}
if (s2 == 5) {
for (int i = 0; i < 5; i++) {
if (b[i].n != c[i].n) return b[i].n > c[i].n;
}
return 0;
}
if (s2 == 4) {
return b[0].n > c[0].n;
}
if (s2 == 3) {
if (b[0].n != c[0].n) return b[0].n > c[0].n;
if (b[3].n != c[3].n) return b[3].n > c[3].n;
return b[4].n > c[4].n;
}
if (s2 == 2) {
if (b[0].n != c[0].n) return b[0].n > c[0].n;
if (b[3].n != c[3].n) return b[3].n > c[3].n;
return b[4].n > c[4].n;
}
if (s2 == 1) {
if (b[0].n != c[0].n) return b[0].n > c[0].n;
for (int i = 2; i < 5; i++) {
if (b[i].n != c[i].n) return b[i].n > c[i].n;
}
return 0;
}
for (int i = 0; i < 5; i++) {
if (b[i].n != c[i].n) return b[i].n > c[i].n;
}
return 0; // default
}
bool check() {
/*
return value:
1 -> win
0 -> lost / draw
*/
// diff kind
if (s1 != s2) return s1 > s2;
if (s1 == 9) return 0;
if (s1 == 8) {
return a[0].n > b[0].n;
}
if (s1 == 7) {
if (a[0].n != b[0].n) return a[0].n > b[0].n;
return a[4].n > b[4].n;
}
if (s1 == 6) {
if (a[0].n != b[0].n) return a[0].n > b[0].n;
return a[4].n > b[4].n;
}
if (s1 == 5) {
for (int i = 0; i < 5; i++) {
if (a[i].n != b[i].n) return a[i].n > b[i].n;
}
return 0;
}
if (s1 == 4) {
return a[0].n > b[0].n;
}
if (s1 == 3) {
if (a[0].n != b[0].n) return a[0].n > b[0].n;
if (a[3].n != b[3].n) return a[3].n > b[3].n;
return a[4].n > b[4].n;
}
if (s1 == 2) {
if (a[0].n != b[0].n) return a[0].n > b[0].n;
if (a[3].n != b[3].n) return a[3].n > b[3].n;
return a[4].n > b[4].n;
}
if (s1 == 1) {
if (a[0].n != b[0].n) return a[0].n > b[0].n;
for (int i = 2; i < 5; i++) {
if (a[i].n != b[i].n) return a[i].n > b[i].n;
}
return 0;
}
for (int i = 0; i < 5; i++) {
if (a[i].n != b[i].n) return a[i].n > b[i].n;
}
return 0; // default
}
void judgec() {
// (huang jia) tong hua shun
if (c[0].c == c[1].c && c[1].c == c[2].c && c[2].c == c[3].c && c[3].c == c[4].c) {
if (c[0].n == 14 && c[1].n == 13 && c[2].n == 12 && c[3].n == 11 && c[4].n == 10) { // AKQJT
s3 = 9;
return;
}
if (c[0].n == c[1].n + 1 && c[1].n == c[2].n + 1 && c[2].n == c[3].n + 1 && c[3].n == c[4].n + 1) { // shun zi
s3 = 8;
return;
}
if (c[0].n == 14 && c[1].n == 5 && c[2].n == 4 && c[3].n == 3 && c[4].n == 2) { // A2345
swap(c[0], c[1]);
swap(c[1], c[2]);
swap(c[2], c[3]);
swap(c[3], c[4]);
c[4].n = 1;
s3 = 8;
return;
}
}
// 4 tiao
if (c[0].n == c[1].n && c[1].n == c[2].n && c[2].n == c[3].n) {
s3 = 7;
return;
}
if (c[1].n == c[2].n && c[2].n == c[3].n && c[3].n == c[4].n) {
swap(c[0], c[4]);
s3 = 7;
return;
}
// 3 dai 2
if (c[0].n == c[1].n && c[1].n == c[2].n && c[3].n == c[4].n) {
s3 = 6;
return;
}
if (c[0].n == c[1].n && c[2].n == c[3].n && c[3].n == c[4].n) {
s3 = 6;
// tiao zheng pai de shun xu
swap(c[0], c[4]);
swap(c[1], c[3]);
return;
}
// tong hua
if (c[0].c == c[1].c && c[1].c == c[2].c && c[2].c == c[3].c && c[3].c == c[4].c) {
s3 = 5;
return;
}
// shun zi
if (c[0].n == c[1].n + 1 && c[1].n == c[2].n + 1 && c[2].n == c[3].n + 1 && c[3].n == c[4].n + 1) { // shun zi
s3 = 4;
return;
}
if (c[0].n == 14 && c[1].n == 5 && c[2].n == 4 && c[3].n == 3 && c[4].n == 2) { // A2345
s3 = 4;
c[0].n = 1;
swap(c[0], c[1]);
swap(c[1], c[2]);
swap(c[2], c[3]);
swap(c[3], c[4]);
return;
}
// 3 tiao
if (c[0].n == c[1].n && c[1].n == c[2].n) {
s3 = 3;
return;
}
if (c[1].n == c[2].n && c[2].n == c[3].n) {
s3 = 3;
c[3] = c[0];
c[0] = c[1];
if (c[3].n < c[4].n) swap(c[3], c[4]);
return;
}
if (c[2].n == c[3].n && c[3].n == c[4].n) {
s3 = 3;
swap(c[1], c[2]);
swap(c[2], c[3]);
swap(c[3], c[4]);
swap(c[0], c[1]);
swap(c[1], c[2]);
swap(c[2], c[3]);
return;
}
// 2 dui
if (c[0].n == c[1].n) {
if (c[2].n == c[3].n) {
s3 = 2;
return;
}
if (c[3].n == c[4].n) {
s3 = 2;
swap(c[2], c[3]);
swap(c[3], c[4]);
return;
}
}
if (c[1].n == c[2].n) {
if (c[3].n == c[4].n) {
s3 = 2;
swap(c[0], c[1]);
swap(c[1], c[2]);
swap(c[2], c[3]);
swap(c[3], c[4]);
return;
}
}
// 1 dui
if (c[0].n == c[1].n) {
s3 = 1;
return;
}
if (c[1].n == c[2].n) {
s3 = 1;
swap(c[2], c[0]);
return;
}
if (c[2].n == c[3].n) {
s3 = 1;
swap(c[0], c[2]);
swap(c[1], c[3]);
return;
}
if (c[3].n == c[4].n) {
s3 = 1;
swap(c[0], c[3]);
swap(c[1], c[4]);
swap(c[2], c[3]);
swap(c[3], c[4]);
return;
}
// san pai
s3 = 0;
return;
}
int main() {
string s;
while (cin >> s) {
if (s == "#") break;
{
int num, cd;
if (s[0] == 'S') cd = 0;
if (s[0] == 'H') cd = 1;
if (s[0] == 'D') cd = 2;
if (s[0] == 'C') cd = 3;
if (s[1] == 'A') num = 14;
else if (s[1] == 'T') num = 10;
else if (s[1] == 'J') num = 11;
else if (s[1] == 'Q') num = 12;
else if (s[1] == 'K') num = 13;
else num = s[1] - '0';
me[0] = {cd, num};
cin >> s;
if (s[0] == 'S') cd = 0;
if (s[0] == 'H') cd = 1;
if (s[0] == 'D') cd = 2;
if (s[0] == 'C') cd = 3;
if (s[1] == 'A') num = 14;
else if (s[1] == 'T') num = 10;
else if (s[1] == 'J') num = 11;
else if (s[1] == 'Q') num = 12;
else if (s[1] == 'K') num = 13;
else num = s[1] - '0';
me[1] = {cd, num};
cin >> s;
if (s[0] == 'S') cd = 0;
if (s[0] == 'H') cd = 1;
if (s[0] == 'D') cd = 2;
if (s[0] == 'C') cd = 3;
if (s[1] == 'A') num = 14;
else if (s[1] == 'T') num = 10;
else if (s[1] == 'J') num = 11;
else if (s[1] == 'Q') num = 12;
else if (s[1] == 'K') num = 13;
else num = s[1] - '0';
en[0] = {cd, num};
cin >> s;
if (s[0] == 'S') cd = 0;
if (s[0] == 'H') cd = 1;
if (s[0] == 'D') cd = 2;
if (s[0] == 'C') cd = 3;
if (s[1] == 'A') num = 14;
else if (s[1] == 'T') num = 10;
else if (s[1] == 'J') num = 11;
else if (s[1] == 'Q') num = 12;
else if (s[1] == 'K') num = 13;
else num = s[1] - '0';
en[1] = {cd, num};
cin >> s;
if (s[0] == 'S') cd = 0;
if (s[0] == 'H') cd = 1;
if (s[0] == 'D') cd = 2;
if (s[0] == 'C') cd = 3;
if (s[1] == 'A') num = 14;
else if (s[1] == 'T') num = 10;
else if (s[1] == 'J') num = 11;
else if (s[1] == 'Q') num = 12;
else if (s[1] == 'K') num = 13;
else num = s[1] - '0';
pu[0] = {cd, num};
cin >> s;
if (s[0] == 'S') cd = 0;
if (s[0] == 'H') cd = 1;
if (s[0] == 'D') cd = 2;
if (s[0] == 'C') cd = 3;
if (s[1] == 'A') num = 14;
else if (s[1] == 'T') num = 10;
else if (s[1] == 'J') num = 11;
else if (s[1] == 'Q') num = 12;
else if (s[1] == 'K') num = 13;
else num = s[1] - '0';
pu[1] = {cd, num};
cin >> s;
if (s[0] == 'S') cd = 0;
if (s[0] == 'H') cd = 1;
if (s[0] == 'D') cd = 2;
if (s[0] == 'C') cd = 3;
if (s[1] == 'A') num = 14;
else if (s[1] == 'T') num = 10;
else if (s[1] == 'J') num = 11;
else if (s[1] == 'Q') num = 12;
else if (s[1] == 'K') num = 13;
else num = s[1] - '0';
pu[2] = {cd, num};
} // input
vis[me[0].c * 20 + me[0].n] = vis[me[1].c * 20 + me[1].n] = vis[en[0].c * 20 + en[0].n] = vis[en[1].c * 20 + en[1].n] = vis[pu[0].c * 20 + pu[0].n] = vis[pu[1].c * 20 + pu[1].n] = vis[pu[2].c * 20 + pu[2].n] = 1;
int ans = 0, cnt = 0;
for (int i = 0; i < 4; i++) {
for (int j = 2; j <= 14; j++) {
for (int k = 0; k < 4; k++) {
for (int l = 2; l <= 14; l++) {
pu[3] = {i, j};
pu[4] = {k, l};
if (!ok(i, j, k, l)) continue;
memset(a, 0, sizeof(a));
memset(b, 0, sizeof(b));
memset(c, 0, sizeof(c));
s3 = s1 = s2 = 0;
card t = {0, 0};
for (int i = 5; i < 9; i++) pu[i] = t;
// select card
for (int m = 0; m < 7; m++) {
for (int n = m + 1; n < 7; n++) {
for (int o = n + 1; o < 7; o++) {
for (int p = o + 1; p < 7; p++) {
for (int q = p + 1; q < 7; q++) {
pu[5] = me[0];
pu[6] = me[1];
c[0] = pu[m];
c[1] = pu[n];
c[2] = pu[o];
c[3] = pu[p];
c[4] = pu[q];
sort(c, c + 5, cmp);
judgec();
if (!cmpa()) {
for (int i = 0; i < 5; i++) a[i] = c[i];
s1 = s3;
}
pu[5] = en[0];
pu[6] = en[1];
c[0] = pu[m];
c[1] = pu[n];
c[2] = pu[o];
c[3] = pu[p];
c[4] = pu[q];
sort(c, c + 5, cmp);
judgec();
if (!cmpb()) {
for (int i = 0; i < 5; i++) b[i] = c[i];
s2 = s3;
}
}
}
}
}
}
// cnt win
ans += check();
cnt++;
}
}
}
}
double pwin = (double)ans / cnt;
cout << fixed << setprecision(20) << pwin << endl;
vis[me[0].c * 20 + me[0].n] = vis[me[1].c * 20 + me[1].n] = vis[en[0].c * 20 + en[0].n] = vis[en[1].c * 20 + en[1].n] = vis[pu[0].c * 20 + pu[0].n] = vis[pu[1].c * 20 + pu[1].n] = vis[pu[2].c * 20 + pu[2].n] = 0;
}
return 0;
}
可以看出,模擬題想出來正解並不難,主要困擾我們的是難以將思想轉化為程式碼。
只要多加練習,再難的模擬題都能做出來!
作業
- [CSP-S 2023] 結構體
- [THUPC 2023 初賽] 亂西星上的空戰