【牛客小白月赛25】
A-AOE还是单体?
这道题很简单,理解题目就是:
- 单伤1点mp,群伤x点mp,杀光最少要多少mp。
算法解析:
- 这道题唯一特殊的就是群伤,群伤就是对多个个体进行单伤。所以如果要群伤,就必须有用群伤的理由。
- 用群伤的理由是什么呢?就是比单伤所要花的mp要少。所以如果群伤花x点,打的伤害却比x要少,岂不是就亏了。
- 所以我们一直打群伤,打到怪物只剩x个以下为止。
操作分析:
- 既然是打到对手只剩下x为止,所以我们群伤次数就是第n - k大数。
- 然后后面每个都是单伤,我们就统计起来就好了。
- 所以为了操作方便,我们排个序,上面的都可以O(1)查询了。
打代码嘿:
- 初始化题目数组。
- 给这数组排个序。
- 判断一下,如果n比x要小,就直接全部打单伤。把总和输出就好了(单伤伤害为1)。
- 如果不是的话,就先打出a[n - k]次群伤,消耗a[n - k] * x点mp。后面的就每个减去a[n - k](被群伤消耗的)累加起来就好了。(用n - k是因为我们舍弃了a[0],从1开始的)。
- 就算出来咯,看代码~
AC代码
#include <iostream> #include <algorithm> using namespace std; typedef long long ll; #define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); //代码预处理区 const int MAX = 2e5 + 7; ll a[MAX]; //全局变量区 int main() { IOS; int n, x; cin >> n >> x; for (int i = 1; i <= n; i++) cin >> a[i]; sort(a + 1, a + 1 + n); ll mp = 0; if (n > x) { mp = a[n - x] * x; for (int i = n - x + 1; i <= n; i++) mp += a[i] - a[n - x]; } else for (int i = 1; i <= n; i++) mp += a[i]; cout << mp << endl; return 0; } //函数区
D-抽卡
这道题首先是一道数学题,然后才扯到的逆元。
首先,我们要知道,我们用数学该怎么把这个概率算出来:
- 我们都知道,如果要中奖,只要至少有一个中奖就可以了的概率。也可以说是:总概率 - 中不了奖的概率。
- 为什么要用中不了的概率呢?因为好算呀,中不了的概率就是每个卡池都失败的概率之积。
- 也就是说,其实我们的答案就是1 - 失败概率,在mod下的一个值。
逆元:
- 简单来说就是一个数的倒数。
- 但是我们在mod取余的情况下就不一样了,除法是不能直接做的,只能用分子 * 分母的逆元。
- 分母的逆元怎么算呢?我们这里就要用到费马小定理了:a与p互质时就有:ap-1 ≡ 1(mod p)。
- 所以1/a = ap-2。再用取模快速幂就好了。
打代码嘞:
- 先保存题目要求数组。
- 然后分别将分子和分母求出来。
- 最后用逆元把公式变成代码。
- 看我代码嘞~
AC代码
#include <iostream> using namespace std; typedef long long ll; #define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); //代码预处理区 const int MOD = 1e9 + 7; const int MAX = 1e5 + 7; int n, a[MAX], b[MAX]; //全局变量区 ll calc(ll down, ll up);//计算up/down的值 ll modpow(ll n, ll m);//取模快速幂 //函数预定义区 int main() { IOS; cin >> n; for (int i = 1; i <= n; i++) cin >> a[i]; for (int i = 1; i <= n; i++) cin >> b[i]; int up = 1, down = 1;//分子和分母 for (int i = 1; i <= n; i++) { up = 1ll * up * (a[i] - b[i]) % MOD; down = 1ll * down * a[i] % MOD; } cout << (1 - calc(down, up) + MOD) % MOD << endl; return 0; } ll calc(ll down, ll up) { return up * modpow(down, MOD - 2) % MOD; } ll modpow(ll n, ll m) { ll mul = 1; while (m) { if (m & 1) mul = mul * n % MOD; m >>= 1; n = n * n % MOD; } return mul; } //函数区
E-点击消除
这道题我想过几种解法:去重呀,差分啊,dp嘞。但是时间复杂度绝对爆表的,直接放弃。
- 但是对于这道题的性质我是真没想到可以简单用栈来写。
算法解析:
- 这道题,我们可以看到,是相邻的两个数就要删除,删除后相邻的两数也要删除。
- 我们无论是去重还是差分都是从最后从整个字符串来看的,但我们这里阔以从前往后看。
- 从前往后看是啥意思呢?就是从前往后看到重复的就给删掉,删掉之后相邻的自然就可以继续删掉了。
- 栗子:先入栈abc,因为和前面不同所以就好好存进去了。然后我们再存个c,和前面重复了,就把前面的c删了,加上这个就是删了两个。
再进一个b也是一样删掉了。那如果再进一个b,因为前面不是b了,所以没事,就正常进栈。 - 所以满足阔以轻易添加删除,与从前往后这两个性质的结构,就是栈了。
操作分析:
- 从前往后遍历栈,我们就是碰到相同即删除,不同即进栈就行了(同上面例子)。
打代码哈:
- 先保存好我们的字符串。
- 然后从前往后入栈并按上面判断操作,一条龙去重。
- 然后把栈倒过来,再输出就好了。
- 看我代码~
AC代码
#include <iostream> #include <string> #include <stack> using namespace std; #define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); //代码预处理区 stack<char> st, t; //全局变量区 int main() { IOS; string s; cin >> s; int len = s.length(); for (int i = 0; i < len; i++) if (st.empty()) st.push(s[i]); else { char ch = st.top(); if (ch == s[i]) st.pop(); else st.push(s[i]); } if (st.empty()) cout << 0 << endl; else { while (!st.empty()) { t.push(st.top()); st.pop(); } while (!t.empty()) { cout << t.top(); t.pop(); } cout << endl; } return 0; } //函数区
F-疯狂的自我检索者
货真价实大水题:
- 当不知道的人全部是1时最少,不知道的人全部为5时最多。
打代码呗:
- 输入我们的参数。
- 然后输入已知得分,把和求出来就好了。
- 然后特判一下n是不是等于m(防止被除数为0)。
- 最后直接最大和最小情况分别进行求平均值就好。
- 看代码咯~
AC代码
#include <iostream> using namespace std; //代码预处理区 int main() { int n, m; scanf("%d %d", &n, &m); int sum = 0; for (int i = 1; i <= n - m; i++) { int x; scanf("%d", &x); sum += x; } if (n == m) printf("%.5f %.5f\n", 1.0, 5.0); else printf("%.5f %.5f\n", 1.0 * (sum + m) / n, 1.0 * (sum + 5 * m) / n); return 0; } //函数区
G-解方程
这道题就是看到是个方程就知道是个简单的二分。
再看一遍就发现有两种情况:ab同号,ab异号。
梅开三度(问了大佬),我才发现,题目说ab都是正数!!(我瞎,我超瞎,我sa)orz。
好了好了,二分:
- 二分咋整,就是根据题目意思,我们先看函数。
- 函数里面可以看出来幂次函数和对数函数都一定是单调递增的,所以我们的这个函数也是单调递增的。
- 判断到pow(mid, a) + b * log(mid) >= c时,就说明,答案大了,就把右边的区间不要了。反之亦然。
打代码哈:
- 先保存变量咯。
- 然后二分查找,按照上面的方法来就好了。
- 看代码哈~
AC代码
#include <iostream> #include <cmath> using namespace std; //代码预处理区 const int INF = 0x3f3f3f3f; const double eps = 1e-7; int a, b, c; //全局变量区 inline bool judge(double mid) { if (1 == a) return mid + b * log(mid) >= c; if (2 == a) return mid * mid + b * log(mid) >= c; if (3 == a) return mid * mid * mid + b * log(mid) >= c; } //函数预定义区 int main() { scanf("%d %d %d", &a, &b, &c); double l = 0, r = INF; while ((l + r) / 2 - l > eps) { double mid = (l + r) / 2; if (judge(mid)) r = mid; else l = mid; } printf("%.14f", l); return 0; } //函数区
H-神奇的字母(二)
货真价实二水题:
- 每个字母散列到数组里面统计一下个数,再给输出最多的那个数就好了。
散列:
- 我们就把a~z这26个字母,用ASCII码值散列到0~25的数组中:a[ch - 'a']++。
打代码嘞:
- 输入参数(因为可以换行,所以就是文件末结束,疯狂循环输入就好了)。
- 读取并散列。
- 数组0~25查找一遍。最大的字母位置+'a'输出就好了。
- 看代码嘞~
AC代码
#include <iostream> using namespace std; #define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); //代码预处理区 int chs[26]; //全局变量区 int main() { IOS; char ch; while (cin >> ch) if ('a' <= ch && ch <= 'z') chs[ch - 'a']++; int ans = 0; for (int i = 1; i < 26; i++) if (chs[ans] < chs[i]) ans = i; cout << char(ans + 'a') << endl; return 0; } //函数区
I-十字爆破
这道题一眼看出来的前缀和,但是我瞎,很瞎,超级瞎orz(我以为n和m范围都是1e6,范围都不知道炸到哪里去了)
那为啥说这个可以一眼看出来是前缀和呢?
- 因为我们看题目就知道,每一个位置上的值都是行列所有值之和。
算法操作:
- 既然说是行列所有项相加,也就可以说是行和 + 列和 - 本位(重复)值。
- 也就是说我们只要存下来所有行和(row[MAX])与所有列和(column[MAX]),还有本位值就好了。
- 这里唯一要注意的就是,本位要存下来,我们知道n * m <= 1e6。我们当然不能开一个1e12的数组,所以这里阔以用指针分配,也可以直接用vector容器。
打代码呗:
- 先用容器把我们的矩阵与前缀和保存好。
- 然后用上面的公式直接输出就好啦。
- 看我代码呗~
AC代码
#include <iostream> #include <vector> using namespace std; typedef long long ll; #define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); //代码预处理区 const int MAX = 1e6 + 7; vector<int> mp[MAX]; ll row[MAX], column[MAX]; //全局变量区 int main() { IOS; int n, m; cin >> n >> m; for (int i = 1; i <= n; i++) { mp[i].push_back(0); for (int j = 1; j <= m; j++) { int x; cin >> x; mp[i].push_back(x); row[i] += x;//求行和 column[j] += x;//求列和 } } for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) cout << row[i] + column[j] - mp[i][j] << " "; cout << endl; } return 0; } //函数区
比赛 文章被收录于专栏
憨憨的专栏