牛客周赛46(已补)
比赛链接:牛客周赛46
赛时感受
本场参加的是内测,多亏了内测群的佬提供的思路,得以AK。
ABC都是简单的签到题,D稍微需要分类一下,EF有点算法知识,E可以使用前缀和+二分搜索过掉,但是听说好像还能使用离散化树状数组等等,F是数学知识,隔板法和求质数、求组合。
一开始脑袋懵了,以为C题的数据太大暴力过不了,转去写欧拉筛求质数妄图通过质数求出x的小于n的因子个数,后面发现更难处理了,后面突然想起直接对x暴力求解是O(n½)的时间复杂度,然后直接暴力过掉。F题思考了很久,最开始连暴力的思路也没有,后面求出了x可以拆出来的质数个数,本来想对每个位置求组合,但是对于x = 4, y = 3,x = 2 * 2,可能第一个乘数排列得到第一个2,第二个乘数可能排列得到第二个2,但是同样可能第一个乘数排列取到第二个2,第二个乘数排列取到第一个2,但是此时两种情况是相同的,重复计算了同一种情况,一直卡在这个点,还是数学薄弱了,最后是群里的佬说可以用隔板法才反应过来。
A
思路
在有热的抹茶的时候先吃热抹茶,再吃冰抹茶,当冰抹茶吃完但是热抹茶还有时,需要判断当前还能吃热抹茶吗,若热抹茶吃完了病抹茶还没吃完,就直接吃掉剩下所有的冰抹茶。
题解
# python s = list(map(int, input().split())) if s[0] > s[1] * 2: print(s[1] + s[0]) else : print(s[0] + s[0] // 2)
B
思路
将输入的第x种茶的美味值赋值为0,然后对没位置数组进行排序,计算没位置最大的茶的种数就可以知道有多少种红茶可以选择了。
代码
#include <bits/stdc++.h> using namespace std; #define ll long long const int N = 1e5 + 10; ll a[N]; int main() { int n, x; cin >> n >> x; for (int i = 1; i <= n; i++) { cin >> a[i]; } a[x] = 0; sort(a + 1, a + 1 + n); int i = n; while (i >= 1) { if (a[i] == a[i - 1]) { i--; } else { break; } } cout << n - i + 1 << endl; return 0; }
C
思路
因为改变第i个灯的开关状态会改变所有编号为i倍数的灯的开关状态,所以需要计算小于n的x的因数个数,当因数个数为奇数时,第x个灯是开着的,否则为关闭的。
暴力枚举:暴力枚举x的因数,枚举到sqrt(x)为止,大于sqrt(x)的因数可以使用x / (小于sqrt(x)的因数)计算得出,同时还需要判断得到的因数是否小于n,注意当当前枚举的元素大于n时就可以退出循环,因为后边枚举的因数不可能会小于n。在枚举时还可能遇到sqrt(x)为整数的情况需要判断枚举的因数是否等于x / (小于sqrt(x)的因数),若相等的话只能视为同一个因数。
题解
#include <bits/stdc++.h> using namespace std; #define ll long long const int N = 1e6 + 10; int main() { ll n, x, ans = 0; cin >> n >> x; for (int i = 1; i <= x / i; i++) { if (x % i == 0 && i <= n) { ans++; if (i != (x / i) && x % (x / i) == 0 && (x / i) <= n) { ans++; } } } cout << (ans % 2 == 0 ? "OFF" : "ON") << endl; return 0; }
D
思路
本题为简单分类题,通过对三个数的其中两个进行mex()操作,并将结果赋值给第三个数。mex()是计算出在括号中没有出现过的最小非负整数,如:mex(0, 2) = 1, mex(2 , 5) = 0。
由于参与mex()运算的数字只有两个,能得到的结果为0,1,2。根据各种情况分类讨论即,由分析可得,k > 2时,若输入的三个数中没有等于k的数字则,k不可能通过mex()计算得到,其他的情况则可以通过推断得出。
题解
#include <bits/stdc++.h> using namespace std; #define ll long long const int N = 1e5 + 10; int main() { int t; cin >> t; while (t--) { ll a[4], k; cin >> a[1] >> a[2] >> a[3] >> k; { sort(a + 1, a + 4); if (a[1] == k || a[2] == k || a[3] == k) { cout << 0 << endl; } else if (k == 0) { cout << 1 << endl; } else if (k == 1) { if (a[1] == 0 && (a[2] != 1 || a[3] != 1)) { cout << 1 << endl; } else { cout << 2 << endl; } } else if (k == 2) { if (a[1] == 0 && (a[2] == 1 || a[3] == 1)) { cout << 1 << endl; } else if (a[1] == 0) { cout << 2 << endl; } else if (a[1] == 1) { cout << 2 << endl; } else { cout << 3 << endl; } } else { cout << -1 << endl; } } } return 0; }
E
思路
每天都能吃任意数量的猫粮,同一种猫粮他一天只会吃一次,求最大的营养值,所以贪心每天把能吃的猫粮全部吃一遍。
二分查找和结果计算:按猫粮的数量对猫粮进行从小到大的排序,然后二分找出数量小于k的数量最多的猫粮下标。找出数量小于等于k的数量最多的猫粮下标时,直接把这些数量小于等于k的猫粮的营养值之和加到结果上,剩下的猫粮的数量都是大于k的,所以每种猫粮按k天的分量计算也加到结果上,最后得出答案数量小于等于k的各种猫粮所有营养值之和 + 数量大于等于k的各种猫粮每天的分量之和 * k天
。
时间复杂度前缀和优化:由于需要询问q次,所以若每一次都是二分完再去对答案进行o(n)的加法操作,则会出现1e5 * 1e5的时间复杂度,此时还没有包括二分查询的时间复杂度,肯定会超时,所以在查询前可以使用一次前缀和优化,减少计算答案时的加法操作,变成o(1)的查询,此时的时间复杂度为前缀和的预处理o(n) + sort排序o(log(n)) + 二分查找q次o(q * log(n)) = o(q * log(n)),满足题目要求的时间复杂度。
题解
#include <bits/stdc++.h> using namespace std; #define ll long long const int N = 1e5 + 10; struct p { int a, b; }food[N]; bool cmp(struct p x, struct p y) { return x.b < y.b; } ll all_sum[N], sum[N]; int main() { int n; cin >> n; for (int i = 1; i <= n; i++) { cin >> food[i].a; } for (int j = 1; j <= n; j++) { cin >> food[j].b; } sort(food + 1, food + 1 + n, cmp); for (int i = 1; i <= n; i++) { all_sum[i] = food[i].a * food[i].b + all_sum[i - 1]; sum[i] = food[i].a + sum[i - 1]; } int q; cin >> q; for (int i = 1; i <= n; i++) { ll l = 1, r = n, res = 0, mid; int k; cin >> k; while (l <= r) { mid = (l + r) >> 1; if (food[mid].b <= k) { res = mid; l = mid + 1; } else { r = mid - 1; } } cout << all_sum[res] + (sum[n] - sum[res]) * k << endl; } return 0; }
F
思路
F题是数学题,对每个x拆成y个正整数的乘积,很快就会想到要求出x的质因子个数。因为有t次询问,所以需要在询问之前进行一次初始化,查出小于sqrt(1e9)的所有质数,使用欧拉筛prime()。
只需要查出小于sqrt(1e9)的质数是因为需要分解的数字的数据范围为1e9,是因为所有正整数都能写成若干个质数相乘的形式,每个正整数最多只能有一个大于sqrt(本身)的质因子,若有两个或者以上,他们相乘肯定会大于sqrt(本身) * sqrt(本身) = 本身,所以不合理,对于这个可能出现的大于sqrt(本身)的质因子,只需要到时候特判处理就行。
质因子分解:此时对每个询问的数字进行质因子分解,使用初始化时求出的isprime质数数组进行分解,最后所有已知的质因子都被判断了之后,若此时的x还是大于1,则此时的x就是大于sqrt(输入的原x)的质因子。
隔板法:现在对每个质因子的个数使用隔板法计算结果,现有ans个相同质因子(如:2),需要将这些质因子放到y个位置里,使得每种情况都不一样,求出各种情况的种数。所以可以视为在ans个因子中插入y - 1个隔板分成了y份,同样可以视为现有ans + y - 1个位置,从ans + y - 1个位置里组合y - 1个位置放隔板把ans个因子分成y份。
组合数:所以现在就是求组合数的问题,这里使用逆元求组合数,使用费马小定理求除组合数,但是这里还有一个细节,这里的ans是两位数以内,y - 1会是八位数以内,若求C(ans + y - 1, y - 1),则可能会超时因为需要求(y - 1)!,但是C(ans + y - 1, y - 1) = C(ans + y - 1, ans),直接求C(ans + y - 1, ans)就不会超时了。
再将其他质因子对每种质因子也同样求出各种情况的种数,最后将这些种数相乘就是答案。
题解
#include <bits/stdc++.h> using namespace std; #define ll long long const int N = 1e5 + 10; const ll mod = 1e9 + 7; bool vis[N]; int isprime[N]; void prime() { for (int i = 2; i <= 5e4; i++) { if (vis[i] == false) isprime[++isprime[0]] = i; for (int j = 1; j <= isprime[0] && i * isprime[j] <= 5e4; j++) { vis[i * isprime[j]] = true; if (i % isprime[j] == 0) break; } } } ll qsm(ll x, ll y) { ll res = 1; while (y) { if (y & 1) { res *= x; res %= mod; } x *= x; y >>= 1; x %= mod; } return res; } ll inverse(ll x) { return qsm(x, mod - 2); } ll c(ll n, ll m) { ll res = 1; for (ll i = n; i > (n - m); i--) { res *= i; res %= mod; } ll x = 1; for (ll i = 1; i <= m; i++) { x *= i; x %= mod; } res *= inverse(x); return res % mod; } int main() { int t; cin >> t; prime(); while (t--) { ll x, y, ans = 0, res = 1; cin >> x >> y; for (int i = 1; i <= isprime[0]; i++) { if (isprime[i] > x) break; if (x % isprime[i] == 0) { while (x % isprime[i] == 0) { x /= isprime[i]; ans++; } } if (ans == 0) continue; res *= c(ans + y - 1, ans); res %= mod; ans = 0; } if (x > 1) { res *= y; res %= mod; } cout << res << endl; } return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现