C - 滨海公园
SDUQD 旁边的滨海公园有 x 条长凳。第 i 个长凳上坐着 a_i 个人。这时候又有 y 个人将来到公园,他们将选择坐在某些公园中的长凳上,那么当这 y 个人坐下后,记k = 所有椅子上的人数的最大值,那么k可能的最大值mx和最小值mn分别是多少。
Input
第一行包含一个整数 x (1 <= x <= 100) 表示公园中长椅的数目
第二行包含一个整数 y (1 <= y <= 1000) 表示有 y 个人来到公园
接下来 x 个整数 a_i (1<=a_i<=100),表示初始时公园长椅上坐着的人数
Output
输出 mn 和 mx
Input Example
3
7
1
6
1
Output Example
6 13
思路: 贪婪思想,最大值mx就所有人扎堆到本来就多的。最小值mn尽量分散开,去人少的地方。
输入原本每个凳子人数的时候记录下最大值max,max+之后来的就是mx;
然后求最小,先求一下每个凳子与人数最多凳子之差的和sum,如果新来的小于sum,mn就是max,否则将剩下的人分散到各个凳子
#include <iostream>
using namespace std;
int v[105];
int main()
{
int x, y, mx, mn;
cin >> x >> y;
int max = 0, sum = 0;
for (int i = 1; i <= x; i++)
{
cin >> v[i];
max = v[i] > max ? v[i] : max;
}
mx = max + y;
for (int i = 1; i <= x; i++)
sum += (max - v[i]);
if (y <= sum)
mn = max;
else
mn = max + (y - sum) / x + ((y - sum) % x > 0);
cout << mn << " " << mx;
}
B - 东东学打牌
最近,东东沉迷于打牌。所以他找到 HRZ、ZJM 等人和他一起打牌。由于人数众多,东东稍微修改了亿下游戏规则:
所有扑克牌只按数字来算大小,忽略花色。
每张扑克牌的大小由一个值表示。A, 2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K 分别指代 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13。
每个玩家抽得 5 张扑克牌,组成一手牌!(每种扑克牌的张数是无限的,你不用担心,东东家里有无数副扑克牌)
理所当然地,一手牌是有不同类型,并且有大小之分的。
举个栗子,现在东东的 “一手牌”(记为 α),瑞神的 “一手牌”(记为 β),要么 α > β,要么 α < β,要么 α = β。
那么这两个 “一手牌”,如何进行比较大小呢?首先对于不同类型的一手牌,其值的大小即下面的标号;对于同类型的一手牌,根据组成这手牌的 5 张牌不同,其值不同。下面依次列举了这手牌的形成规则:
大牌:这手牌不符合下面任一个形成规则。如果 α 和 β 都是大牌,那么定义它们的大小为组成这手牌的 5 张牌的大小总和。
对子:5 张牌中有 2 张牌的值相等。如果 α 和 β 都是对子,比较这个 “对子” 的大小,如果 α 和 β 的 “对子” 大小相等,那么比较剩下 3 张牌的总和。
两对:5 张牌中有两个不同的对子。如果 α 和 β 都是两对,先比较双方较大的那个对子,如果相等,再比较双方较小的那个对子,如果还相等,只能比较 5 张牌中的最后那张牌组不成对子的牌。
三个:5 张牌中有 3 张牌的值相等。如果 α 和 β 都是 “三个”,比较这个 “三个” 的大小,如果 α 和 β 的 “三个” 大小相等,那么比较剩下 2 张牌的总和。
三带二:5 张牌中有 3 张牌的值相等,另外 2 张牌值也相等。如果 α 和 β 都是 “三带二”,先比较它们的 “三个” 的大小,如果相等,再比较 “对子” 的大小。
炸弹:5 张牌中有 4 张牌的值相等。如果 α 和 β 都是 “炸弹”,比较 “炸弹” 的大小,如果相等,比较剩下那张牌的大小。
顺子:5 张牌中形成 x, x+1, x+2, x+3, x+4。如果 α 和 β 都是 “顺子”,直接比较两个顺子的最大值。
龙顺:5 张牌分别为 10、J、Q、K、A。
作为一个称职的魔法师,东东得知了全场人手里 5 张牌的情况。他现在要输出一个排行榜。排行榜按照选手们的 “一手牌” 大小进行排序,如果两个选手的牌相等,那么人名字典序小的排在前面。
不料,此时一束宇宙射线扫过,为了躲避宇宙射线,东东慌乱中清空了他脑中的 Cache。请你告诉东东,全场人的排名
输入
输入包含多组数据。每组输入开头一个整数 n (1 <= n <= 1e5),表明全场共多少人。
随后是 n 行,每行一个字符串 s1 和 s2 (1 <= |s1|,|s2| <= 10), s1 是对应人的名字,s2 是他手里的牌情况。
输出
对于每组测试数据,输出 n 行,即这次全场人的排名。
样例输入
3
DongDong AAA109
ZJM 678910
Hrz 678910
样例输出
Hrz
ZJM
DongDong
思路:一个比较大小的问题,关键在于如何分配value。value可以是个int。
牌有字母有数字,先统一一下,定义一个结构体表示一张牌
struct card
{
int value;//A为1,JQK分别为11 12 13
operator int()
{
return value;
}
};
定义结构体person
struct person
{
string name;
card cards[5];
int value;//一手牌的大小
};
先输入,输入牌的时候转换为我想要的value(A–1,J–11,Q–12,K–13),
输入为1的时候说明遇到了10,多读一位即可。
然后给每套牌赋value,简单说一下判断方法
先给5张牌排序,这样方便后面判断,这步比较关键
然后开始依次判断,大牌型优先级高
龙牌:找一下牌中有没有A,有的话变成14,然后判断是不是顺子
顺子:已经排好序了,比较一下相邻相等就行了
炸弹:看相邻四个是不是相等
三个和两个差不多,说说两个的
判断对子的方法是遍历牌,如果这张和下张相等则是对子,然后i++,
循环范围是第一张到倒数第二张,最后一张要特殊处理一下
比较分多个层级,可以把给每个层级分配位数,比如是顺子value就加个7 * 106,然后看看顺子的第一张,比如是A,那就加上1 * 104,之所以用到
六次方,是因为最多分三层比较,每层给两位,因为最大k牌value是13
最后一步排序即可
#include <iostream>
#include <sstream>
#include <algorithm>
using namespace std;
const int MAXN = 10000 + 5;
struct card
{
int value;
operator int()
{
return value;
}
};
struct person
{
string name;
card cards[5];
int value;
};
person ps[MAXN];
int indx;
bool shunzi(person& p)
{
if ((p.cards[0] + 1 == p.cards[1]) && (p.cards[1] + 1 == p.cards[2]) &&
(p.cards[2] + 1 == p.cards[3]) && (p.cards[3] + 1 == p.cards[4]))
{
p.value = 7000000 + p.cards[0] * 10000;
return true;
}
return false;
}
bool longshun(person& p)
{
for (int i = 0; i < 5; i++)
if (p.cards[i] == 1)
{
p.cards[i].value = 14;
sort(p.cards, p.cards + 5, [](card l, card r) {
return l.value < r.value;
});
if (shunzi(p))
{
p.value = 8000000;
p.cards[4].value = 1;
return true;
}
p.cards[4].value = 1;
sort(p.cards, p.cards + 5, [](card l, card r) {
return l.value < r.value;
});
break;
}
return false;
}
bool boom(person& p)
{
if (p.cards[0] == p.cards[3])
{
p.value = 6000000 + p.cards[2] * 10000 + p.cards[4] * 100;
return true;
}
if (p.cards[1] == p.cards[4])
{
p.value = 6000000 + p.cards[2] *10000 + p.cards[0] *100;
return true;
}
return false;
}
bool san(person& p)
{
if (p.cards[0] == p.cards[2])
{
if (p.cards[3] == p.cards[4])
p.value = 5000000 + p.cards[0] * 10000 + p.cards[3] * 100;
else p.value = 4000000 + p.cards[0] * 10000 + p.cards[3] * 100 + p.cards[4] * 100;
return true;
}
if (p.cards[2] == p.cards[4])
{
if (p.cards[0] == p.cards[1])
p.value = 5000000 + p.cards[2] * 10000 + p.cards[0] * 100;
else p.value = 4000000 + p.cards[2] * 10000 + p.cards[0] * 100 + p.cards[1] * 100;
return true;
}
if (p.cards[1] == p.cards[3])
{
p.value = 4000000 + p.cards[1] * 10000 + p.cards[0] * 100 + p.cards[4] * 100;
return true;
}
return false;
}
bool dui(person& p)
{
int v1 = 0, v2 = 0, sum = 0;
for (int i = 0; i <= 3; i++)
{
if (p.cards[i] == p.cards[i + 1])
{
v2 = v1 == 0 ? 0 : p.cards[i];
v1 = v1 == 0 ? p.cards[i] : v1;
i++;
}
else sum += p.cards[i];
}
if (p.cards[3] != p.cards[4]) sum += p.cards[4];
int hi = max(v1, v2), lo = v1 + v2 - hi;
if (v1)
{
p.value = 2000000 + hi * 10000 + lo * 100 + sum + (v2 > 0) * 1000000;
return true;
}
return false;
}
void dapai(person& p)
{
for (int i = 0; i < 5; i++) p.value += p.cards[i];
return;
}
void judge(person& p)
{
sort(p.cards, p.cards + 5, [](card l, card r) {
return l.value < r.value;
});
if (longshun(p)) return;
if (shunzi(p)) return;
if (boom(p)) return;
if (san(p)) return;
if (dui(p)) return;
else dapai(p);
return;
}
int main()
{
int n;
while (cin >> n)
{
indx = 0;
for (int i = 0; i < n; i++)
{
person p;
string name, ca; cin >> p.name >> ca;
stringstream ss(ca);
char c;
int x = 0;
while (ss >> c)
{
if (c == '1')
{
ss >> c;
p.cards[x++].value = 10;
}
else if (c == 'A') p.cards[x++].value = 1;
else if (c == 'J') p.cards[x++].value = 11;
else if (c == 'Q') p.cards[x++].value = 12;
else if (c == 'K') p.cards[x++].value = 13;
else p.cards[x++].value = c - '0';
}
p.value = 0;
ps[indx++] = p;
judge(ps[indx - 1]);
}
sort(ps, ps + indx, [](const person& l, const person& r) {
if (l.value == r.value) return l.name < r.name;
return l.value > r.value;
});
for (int i = 0; i < indx; i++) cout << ps[i].name << '\n';
}
}
反思: 踩坑了,开始想用小数来表示value,一直wa,后来改成int才过
A - 咕咕东的目录管理器
目录管理器可以理解为要维护一棵有根树结构,每个目录的儿子必须保持字典序。
现在咕咕东可以在命令行下执行以下表格中描述的命令:
输入
输入文件包含多组测试数据,第一行输入一个整数表示测试数据的组数 T (T <= 20);
每组测试数据的第一行输入一个整数表示该组测试数据的命令总数 Q (Q <= 1e5);
每组测试数据的 2 ~ Q+1 行为具体的操作 (MKDIR、RM 操作总数不超过 5000);
面对数据范围你要思考的是他们代表的 “命令” 执行的最大可接受复杂度,只有这样你才能知道你需要设计的是怎样复杂度的系统。
输出
每组测试数据的输出结果间需要输出一行空行。注意大小写敏感。
时空限制
Time limit 6000 ms
Memory limit 1048576 kB
样例输入
1
22
MKDIR dira
CD dirb
CD dira
MKDIR a
MKDIR b
MKDIR c
CD …
MKDIR dirb
CD dirb
MKDIR x
CD …
MKDIR dirc
CD dirc
MKDIR y
CD …
SZ
LS
TREE
RM dira
TREE
UNDO
TREE
样例输出
OK
ERR
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
9
dira
dirb
dirc
root
dira
a
b
c
dirb
x
dirc
y
OK
root
dirb
x
dirc
y
OK
root
dira
a
b
c
dirb
x
dirc
y
思路: 一定是个树形结构,题目要求根据子目录名字取到,那么是个map,而且马匹又能自动排序,一举两得。
树形结构采用数组形式,每个节点记录他的父节点,子目录存在map中,first是名字,second是index
特别注意有个撤销操作,需要在进行其他操作时把操作过程留在栈里
MKDIR:map加个孩子newNode,加到node数组中,栈中加入pair(“MKDIR”,newNode),撤销时把newNode从node[now]中删除即可
RM:删除仅仅是把孩子从map中删了eraseNode,node中留着方便撤销。栈中加入(“RM”,eraseNode)
CD:直接改变now即可,需要判断一下是否有这个目录或者如果是…是否有夫目录。栈中加入(“CD”,now)
SZ:要统计size就要遍历,极端情况就是整个树,算一算1e5*5000显然太大了,解决方案是每个节点记录其size,那么MKDIR和RM要修改这个值,注意整个树都要修改
LS:就列出来就行
TREE:如果采用遍历手段,极端情况要遍历所有节点,太耗时,采用懒更新的方案,没个节点记录pre和bck。求pre和bck的方案是递归,后来溢栈了,又改成了迭代
UNDO:从栈中取出来,pair的second是需要的信息,MKDIR的撤销的second记录的是创建的目录的index,把这个删了即可。其余操作类似
#include <iostream>
#include <stack>
#include <map>
#include <vector>
#include <cstring>
using namespace std;
constexpr int MAXN = 5000 + 10;
stack<pair<string, int>> s;
struct directory
{
string name;
map<string, int> mp;
int fa = -1, sz = 1;
vector<int> pre, bck;
bool tag = false;
};
directory node[MAXN];
int indx, now;
bool vis[5000];
void init()
{
indx = now = 0;
while (s.size()) s.pop();
directory root;
root.name = "root";
node[indx++] = root;
}
void update(int id, int num)
{
while (id != -1)
{
node[id].tag = false;
node[id].sz += num;
id = node[id].fa;
}
}
void mkdir()
{
string str; cin >> str;
directory& curNode = node[now];
if (curNode.mp.count(str))
{
cout << "ERR'\n";
return;
}
cout << "OK\n";
directory newNode;
newNode.name = str;
newNode.fa = now;
node[indx++] = newNode;
curNode.mp[str] = indx - 1;//indx维持为尾后
s.push(make_pair("MKDIRS", indx - 1));
update(now, 1);
}
void rm()
{
string str; cin >> str;
directory& curNode = node[now];
if (!curNode.mp.count(str))
{
cout << "ERR\n";
return;
}
int eraseNode = curNode.mp[str];
cout << "OK\n";
curNode.mp.erase(str);
update(now, -node[eraseNode].sz);
s.push(make_pair("RM", eraseNode));
}
void cd()
{
string str; cin >> str;
directory& curNode = node[now];
if (!(curNode.mp.count(str) || ((str == "..") && node[now].fa != -1)))
{
cout << "ERR\n";
return;
}
cout << "OK\n";
int theNode;
if (str == "..") theNode = curNode.fa;
else theNode = curNode.mp[str];
s.push(make_pair("CD", theNode));
now = theNode;
return;
}
void sz()
{
cout << node[now].sz << '\n';
return;
}
void ls()
{
int t = node[now].mp.size();
if (t == 0)
{
cout << "EMPTY\n";
return;
}
directory& curNode = node[now];
auto pos = curNode.mp.begin();
if (t >= 1 && t <= 10)
{
while (pos != curNode.mp.end())
cout << (pos++)->first << '\n';
return;
}
for (int i = 1; i <= 5; i++)
cout << (pos++)->first << '\n';
cout << "...\n";
pos = curNode.mp.end();
for (int i = 0; i < 5; i++) pos--;
for (int i = 0; i < 5; i++)
cout << (pos++)->first << '\n';
}
//弃用,因为溢栈了
//void _preTrack(int id, int cur)
//{
// if (node[id].pre.size() >= 10) return;
// node[id].pre.push_back(cur);
// for (auto c : node[cur].mp)
// _preTrack(id, c.second);
//}
void preTrack(int id)
{
auto& curNode = node[id];
stack<int> s;
s.push(id);
while (!s.empty()) {
int tmp = s.top();
s.pop();
curNode.pre.push_back(tmp);
if (curNode.pre.size() == 10) return;
for (auto i = node[tmp].mp.rbegin(); i != node[tmp].mp.rend();i++)
s.push((*i).second);
}
}
//void _bckTrack(int id, int cur)
//{
// if (node[id].bck.size() >= 5) return;
// for (auto i = node[cur].mp.rbegin(); i != node[cur].mp.rend(); i++)
// _bckTrack(id, (*i).second);
// node[id].bck.push_back(cur);
//}
void bckTrack(int id)
{
memset(vis, 0, sizeof(bool) * 5000);
auto& curNode = node[id];
stack<int> s;
vector<int> v;
s.push(id);
while (v.size() < 5)
{
int tmp = s.top();
while (node[tmp].mp.size() && !vis[tmp])
{
vis[tmp] = true;
for (auto i = node[tmp].mp.begin(); i != node[tmp].mp.end(); i++)
{
s.push((*i).second);
}
tmp = (*(node[tmp].mp.rbegin())).second;
}
v.push_back(s.top()); s.pop();
}
for (auto i = v.rbegin(); i != v.rend(); i++)
curNode.bck.push_back(*i);
}
void pushdown(int id)
{
directory& theNode = node[id];
theNode.pre.clear();
theNode.bck.clear();
preTrack(id);
if (theNode.sz > 10) bckTrack(id);
else theNode.bck = theNode.pre;
theNode.tag = 1;
}
void tree()
{
directory& curNode = node[now];
if (!curNode.tag) pushdown(now);
if (curNode.sz == 1) cout << "EMPTY\n";
else if (curNode.sz > 1 && curNode.sz <= 10)
for (auto c : curNode.pre) cout << node[c].name << '\n';
else
{
for (int i = 0; i < 5; i++) cout << node[curNode.pre[i]].name << '\n';
cout << "..\n";
for (auto i = curNode.bck.begin(); i != curNode.bck.end(); i++)
cout << node[*i].name << '\n';
}
}
void undo()
{
if (s.empty())
{
cout << "ERR\n";
return;
}
cout << "OK\n";
auto e = s.top(); s.pop();
if (e.first == "MKDIR")
{
node[now].mp.erase(node[e.second].name);
update(now, -1);
}
else if (e.first == "RM")
{
node[now].mp[node[e.second].name] = e.second;
update(now, node[e.second].sz);
}
else
{
now = e.second;
}
}
int main()
{
int T; cin >> T;
while (T--)
{
init();
int Q; cin >> Q;
while (Q--)
{
string op; cin >> op;
if (op == "MKDIR") mkdir();
else if (op == "RM") rm();
else if (op == "CD") cd();
else if (op == "SZ") sz();
else if (op == "LS") ls();
else if (op == "TREE") tree();
else if (op == "UNDO") undo();
}
}
}
反思: 这个题的精髓在于size和tree操作,都要记录下来,以空间换时间,否则会超时,这类题要先想好时间复杂度,csp的题空间一般给的比较足,必要时可以用来换时间。