直接按题目规则模拟 m 次洗牌,再从洗完后的牌堆中按发牌位置取出第 p 个玩家的 4 张牌。
OJ: luogu
题目 ID: P2348
难度:入门
标签:模拟
日期: 2026-06-19 03:02
题意
有 n 个玩家、k 张牌,每个玩家需要发 4 张牌。
整副牌先按题目给定的规则洗 m 次,再从上到下依次发牌:
- 第
1张发给1号玩家; - 第
2张发给2号玩家; - …
- 第
n+1张又回到1号玩家。
现在只问第 p 位玩家最后拿到的 4 张牌。
如果总牌数不够给所有玩家每人 4 张,就输出:
Error:cards not enough
思路
这题没有隐藏算法,本质就是一道直接模拟题。
先看最直观的写法:每次洗牌都新建一个牌堆,然后严格按题目给出的顺序把旧牌堆重排过去。
这个版本最容易理解:
cpp
#include <bits/stdc++.h>
using namespace std;
struct Card {
string suit_point;
string kind;
};
int n, k, m, p;
vector<Card> deck;
vector<Card> shuffle_once(const vector<Card> &cur) {
int len = (int) cur.size();
int half = len / 2;
vector<Card> nxt;
// 朴素做法:直接按题目给出的顺序重新构造一副新牌。
for (int i = 0; i < half; i++) {
nxt.push_back(cur[half + i]);
nxt.push_back(cur[i]);
}
if (len % 2 == 1) {
nxt.push_back(cur[len - 1]);
}
return nxt;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> k >> m >> p;
deck.resize(k);
for (int i = 0; i < k; i++) {
cin >> deck[i].suit_point >> deck[i].kind;
}
if (k < 4 * n) {
cout << "Error:cards not enough\n";
return 0;
}
for (int i = 1; i <= m; i++) {
deck = shuffle_once(deck);
}
for (int i = p - 1, cnt = 0; i < k && cnt < 4; i += n, cnt++) {
cout << deck[i].suit_point << ' ' << deck[i].kind << '\n';
}
return 0;
}一次洗牌怎么做
设当前牌堆从上到下编号为:
1, 2, 3, ..., k
题目说一次洗牌后顺序变成:
floor(k/2)+1, 1, floor(k/2)+2, 2, ...
这说明我们可以把牌堆分成两部分:
- 前半段:
1..floor(k/2) - 后半段:
floor(k/2)+1..k
然后按“后半一张、前半一张”的顺序交错放到新牌堆里。
如果 k 是奇数,后半段会多出最后一张牌,它就直接落到新牌堆末尾。
为什么发牌位置很好找
洗牌全部完成后,再开始发牌。
由于发牌始终按玩家编号循环,所以第 p 位玩家拿到的一定是最终牌堆中的:
- 第
p张; - 第
p+n张; - 第
p+2n张; - 第
p+3n张。
因此我们根本不需要再单独模拟“谁拿了哪张”,直接按下标跳着取就行。
正式做法
- 若
k < 4n,直接输出错误信息; - 否则模拟
m次洗牌; - 最后输出位置
p, p+n, p+2n, p+3n上的牌。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
const int MAXK = 100000 + 5;
struct Card {
string suit_point;
string kind;
};
int n, k, m, p;
Card a[MAXK], b[MAXK];
void shuffle_once() {
int half = k / 2;
int pos = 0;
// 新牌堆按“后半张、前半张”交错放入。
for (int i = 1; i <= half; i++) {
b[++pos] = a[half + i];
b[++pos] = a[i];
}
if (k % 2 == 1) {
b[++pos] = a[k];
}
for (int i = 1; i <= k; i++) {
a[i] = b[i];
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> k >> m >> p;
for (int i = 1; i <= k; i++) {
cin >> a[i].suit_point >> a[i].kind;
}
if (k < 4 * n) {
cout << "Error:cards not enough\n";
return 0;
}
for (int i = 1; i <= m; i++) {
shuffle_once();
}
int need = 0;
for (int i = p; i <= k && need < 4; i += n) {
cout << a[i].suit_point << ' ' << a[i].kind << '\n';
need++;
}
return 0;
}复杂度
- 时间复杂度:
- 空间复杂度:
总结
这题的关键不是算法技巧,而是把题面描述准确翻译成代码。
只要把“一次洗牌”的顺序处理对,再抓住“第 p 个玩家对应最终牌堆等差位置”这一点,就能稳定写对。