HUAS 2018暑假第一周比赛-题解
小朋友们有问题评论区 😃
B. 子串计算
难度系数 : ☆
Main idea : 模拟
暴力
按照题目的要求一步一步来就行了
之所以可行的原因是从左往右扫,如果扫到一个子串,把它删除掉之后,假设当前位置的下标是\(i\),新的子串只会出现在\(i-26\)的后面。
时间复杂度\(O(T\cdot |S|)\)
#include<bits/stdc++.h>
using namespace std;
int main()
{
int t;
scanf("%d", &t);
while(t--){
string a, b;
cin >> a >> b;
int ans = 0, la = a.size();
for (int i = 0; i < b.size(); ++i){
if (i + 1 >= la && b.substr(i + 1 - la, la) == a){
++ans;
b.erase(i + 1 - la, la);
i -= la;
}
}
cout << ans << endl;
}
return 0;
}
C. 栈的游戏
难度系数 : ★
Main idea : 思维
首先思考一下,什么才是最优策略呢?
假设现在Dum面对的栈从顶到底是11101,他现在有很多种拿法,怎么拿最优呢?此时显然拿一个1是最优的,这样这个栈下次还是只能他拿,那为什么不拿两个1?因为比起拿两个1,拿一个1可以让他”活的更久些!“
假设现在Dum面对的栈从顶到底是10011,他现在拿100是最优的,这样就不会把这个栈的主导权让给Dee,使得这个栈成为永远只被Dum拿的“奴隶!”
通过上面的思考,一个初始时栈顶为1的栈永远只会被Dum拿,且Dum最多可以拿\(Num_1\)次,
\(Num_1\)指的是栈里面1的个数。
同理可得Dee最多可以拿的次数\(Num_0\)。
那么如果Dee先开局,只要\(\sum Num_0 > \sum Num_1\),Dee就能赢。
同理其他的情况类似。
时间复杂度\(O(T\cdot n\cdot |S|)\)。
# include <bits/stdc++.h>
using namespace std;
char name[5], Stack[55];
int main ()
{
int T, n;
scanf("%d", &T);
while (T--) {
scanf("%d %s", &n, name);
int num_0 = 0, num_1 = 0;
for (int i = 1; i <= n; ++i) {
scanf("%s", Stack);
int len = strlen(Stack);
for (int j = 0; j < len; ++j) {
if (Stack[j] == Stack[0]) {
if (Stack[0] == '0') ++num_0;
else ++num_1;
}
}
}
if (name[1] == 'e') puts(num_0 > num_1 ? "Dee" : "Dum");
else puts(num_1 > num_0 ? "Dum" : "Dee");
}
return 0;
}
E. 字符串挑战赛
难度系数 : ★
Main idea : 模拟
暴力
使用一个bool数组mark[c][num]表示字符c是否连续出现了num次,可以遍历一次字符串预处理出这个数组。
然后对于每一个询问\(m_i\),枚举从\(a\)到\(z\)的字符,如果是\(Yes\)的情况,那么必然有一个字符\(x\)满足
\(Val_x\cdot Num_x = m_i\),其中\(Val_x\)表示字符\(x\)的价值,\(Num_x\)表示字符\(x\)连续出现的次数。那么只需要看看\(mark[x][\frac{m_i}{x}]\)是不是为\(true\)就好了。
时间复杂度\(O(26\cdot M + |S|)\)
# include <bits/stdc++.h>
using namespace std;
const int N = 100005;
char s[N];
bool mark[30][N];
int main ()
{
int n, x;
scanf("%s %d", s + 1, &n);
int len = strlen(s + 1);
int num = 0;
for (int i = 1; i <= len; ++i) {
if (i != 1 && s[i] != s[i - 1]) num = 1;
else ++num;
mark[s[i] - 'a' + 1][num] = true;
}
for (int i = 1; i <= n; ++i) {
scanf("%d", &x);
bool mark = false;
for (int j = 1; j <= 26; ++j) {
if (x % j || x / j > len || mark[j][x / j] == false) continue;
mark = true;
}
puts(mark ? "Yes" : "No");
}
return 0;
}
F. 较小元素
难度系数 : ★
Main idea : STL
set
有不下十几种方法解决这个问题,介绍一个最容易写的。
从前往后遍历数组,维护一个\(set\),对于数组的当前元素\(a_{current}\),使用\(set\)的\(lower\_bound\)函数查找\(set\)里面满足\(\ge a_{current}\)的元素里的最小元素,再把\(a_{current}\)插入\(set\)里面就行了。
但是题目问的是当前元素前面的元素里满足\(<a_{current}\)的元素里的最大元素。
怎么办?
首先把数组里的每个元素取个相反数就行了。
# include <bits/stdc++.h>
using namespace std;
const int N = 100005;
set<long long> s;
int main ()
{
int n;
long long x;
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
scanf("%lld", &x);
x = -x;
auto iter = s.lower_bound(x + 1);
if (iter == s.end()) puts("-1");
else printf("%lld\n", -*iter);
s.insert(x);
}
return 0;
}
D. IPC训练者
难度系数 : ★★
Main idea : 贪心
STL
优先队列
我们从第一天依次考虑到最后一天,那么显然每一天应该要让当前能用的教练中扎心值最大的教练上课,所以需要一个数据结构支持查询最大值,插入元素,删除最大值。显然\(STL\)里的\(priority\_queue\)可以完美实现这些要求。
时间复杂度\(O(T\cdot M\cdot log(N))\)。
# include <bits/stdc++.h>
using namespace std;
const int N = 100005;
struct Node{
int beg, day, s;
}node[N];
struct cmp{
bool operator () (Node a, Node b) {
return a.s < b.s;
}
};
priority_queue<Node, vector<Node>, cmp> que;
bool comp(Node a, Node b) {
return a.beg < b.beg;
}
int main ()
{
int T, n, D, beg, day, s;
scanf("%d", &T);
Node tmp;
while (T--) {
scanf("%d %d", &n, &D);
for (int i = 1; i <= n; ++i) scanf("%d %d %d", &node[i].beg, &node[i].day, &node[i].s);
sort(node + 1, node + n + 1, comp);
int now = 1;
for (int i = 1; i <= D; ++i) {
while (now <= n && node[now].beg == i) que.push(node[now]), ++now;
if (!que.empty()) {
tmp = que.top();
que.pop();
--tmp.day;
if (tmp.day) que.push(tmp);
}
}
long long ans = 0;
while (!que.empty()) ans += (long long)que.top().day * que.top().s, que.pop();
printf("%lld\n", ans);
}
return 0;
}
A. 脆弱核心
难度系数 : ★★★
Main idea : 栈
首先可以很轻易得想到了暴力的做法,就是一天一天的模拟,每天扫描一下数组,把满足条件的元素删掉,直到某一天删不掉任何一个元素为止,这样的时间复杂度为\(O(n^2)\),为什么?因为一天最少删除\(1\)个元素,而最多只能删\(n-2\)个元素,所以最坏的情况下,需要模拟\(n-2\)天。这样是显然过不了\(n=10^5\)的数据的,一般来说,oj
时限\(1s\)可以run \(O(10^8)\)。
不妨考虑简化一下问题,如果我们只需要求出最后会剩下哪些元素,那么可以用栈来得到一个\(O(n)\)的办法,伪代码如下:
从左到右扫描数组,
1.如果现在栈里面的元素个数\(\le1\),那么把当前元素压入栈。
2.设当前栈顶元素为\(a[top]\),现在要处理的元素为\(a[i]\)。
-----> 1).如果$a[top] < a[top-1] $ 且 $ a[top] < a[i]$,弹出当前栈顶元素,转到2.
-----> 2).否则把当前元素压入栈。
扫描完数组后,栈里面的元素就是最后会剩下的元素了。
为什么? 和B题类似的思路。
理解好上面的思想之后就让我们回到原问题,原问题还需要输出每个元素会在哪天被删掉。
我们上面的办法只能求出不会被删掉的元素,所以我们还需要进一步的思考。
如果你真的理解了上面的伪代码了的话,其实剩下该做的事情也不难了,上面的伪代码不仅告诉了我们
哪些元素会被删掉,还告诉了我们每个元素是被它旁边的哪两个元素给删掉的,不访假设\(a[i]\)是被\(a[l]\)和\(a[r]\)给删掉的\((l \le i \le r)\),那么也就是说,\(a[i]\)一定会比\(a[l+1]...a[i-1], a[i+1]...a[r-1]\)晚删掉,多晚呢?必然是这些元素全部被删掉的那一天再加一,为什么?考虑反证法。
时间复杂度\(O(N\cdot T)\)
具体实现的细节看代码吧。
# include <bits/stdc++.h>
using namespace std;
const int N = 100005;
int ans[N], a[N], st[N], Max[N], top;
int main ()
{
int T, n, x;
scanf("%d", &T);
while (T--) {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) ans[i] = Max[i] = 0;
top = 0;
for (int i = 1; i <= n; ++i) scanf("%d", a + i);
for (int i = 1; i <= n; ++i) {
while (top >= 2 && a[st[top]] < a[st[top - 1]] && a[st[top]] < a[i]) {
ans[st[top]] = max(Max[st[top - 1]], Max[st[top]]) + 1;
Max[st[top - 1]] = ans[st[top]];
--top;
}
st[++top] = i;
}
for (int i = 1; i <= n; ++i) printf(i == 1 ? "%d" : " %d", ans[i]);
puts("");
}
return 0;
}