忙里偷闲来更新一下我缓慢的学习进度(),这篇主要还是备战下周蓝桥杯做的题目,还有上星期刚打完的校赛的补题。
周末蓝桥杯rp++
K - 给点…资金
题目链接:https://ac.nowcoder.com/acm/contest/130737/K
一道二分题,当时赛场上想到了的暴力模拟方法,没想到可以二分优化。后面问了其他大佬之后一语点醒梦中人了。
大致题意:有个价值为卡牌,商店在第天有相应的倍率。在第天出售第张牌可以获得 元。每张牌只能出售一次,但可以自己决定哪一天出售。现希望可以尽快卖到元,并输出最快达到元的是第几天。
思路:根据题意我们可知,想要收益最大化肯定是要尽可能让在高倍率的天出售高价值的卡牌。那么我们可以先对数组从大到小排列,然后让在对应的天数区间里面按大倍率配大价值的规律计算。这里查找可能的天数时采用了二分答案,优化我们搜索的过程,否则会超时。最终输出满足条件的最小天数。
我们这里用了一个数组来存入我们选取的第天内的数组存有的值。然后我们按从大到小排列,与数组一一相乘后计入总和。如果达到就继续向左取半搜索,看看有没有更小的值。没有达到就向右取半搜索,找到第一个满足条件的。这里因为二分查找的时间复杂度为,所以这种解法的最终复杂度其实只有,可以轻松通过这道题目。
#include <bits/stdc++.h>
using namespace std;using ll = long long;#define endl '\n'#define mod 998244353
void solved(){ ll n, x; cin >> n >> x; vector<ll> A(n); vector<ll> B(n); for (int i = 0; i < n;i++){ cin >> A[i]; } for (int i = 0; i < n;i++){ cin >> B[i]; } sort(A.begin(), A.end(), greater<ll>()); ll l = 1, r = n, ans = -1; while(l <= r){ ll mid = (l + r) / 2; vector<ll> C(B.begin(), B.begin() + mid); sort(C.begin(), C.end(),greater<ll>()); ll sum = 0; for (ll i = 0; i < mid;i++){ sum += A[i] * C[i]; if(sum >= x){ break; } } if(sum >= x){ r = mid - 1; ans = mid; } else{ l = mid + 1; } } cout << ans << '\n';}
int main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); solved(); return 0;}P10426 宝石组合
需要化简题目给的条件。然后再找到最小的可能的因数。只不过后者代码上也不是那么好实现。
题目链接:https://www.luogu.com.cn/problem/P10426
大致题意:给定个属性值为的宝石。我们会随机从这枚宝石里面选出三枚来进行组合,组合成的精美程度计算方式为 ,表示的是最小公倍数。现在要求输出最高的组合,如果有多个方案只需输出字典序最小的三个。
思路:这题我们需要先对原式进行化简,题解里的大佬都是各显神通的,我就不掺和了。()我这里直接使用公式, 。代入原式可以解得。所以我们的问题就转化为要使得组合成的三个数的最大公因数最大了。
解决方法还是有点需要一点技巧的。我们需要先找出所有宝石里面的有的因数。然后用记录其出现的数量。这里优化复杂度的方法是找到一个因数之后直接用原数除于它就可以找到另外一个对应较大因数。(如果另一个因数是本身就不计入)计入对应的中。接着我们倒序查找出现次数大于等于次的因数的最大值并用记录。然后我们正序从小到大判断是的因数的数并输出前三个即可。
#include <bits/stdc++.h>
using namespace std;using ll = long long;#define endl '\n'#define mod 1000000007LLtypedef pair<int, int> pii;
vector<ll> H;int cnt[200010];
void solved(){ int n; int x = 0; cin >> n; H.resize(n + 1); for (int i = 1; i <= n;i++){ cin >> H[i]; for (int j = 1; j * j <= H[i];j++){ if(H[i] % j == 0){ if(j * j != H[i]) cnt[H[i] / j]++; cnt[j]++; } } } sort(H.begin() + 1, H.begin() + 1 + n); for (int i = 1e5; i >= 1;i--){ if(cnt[i] >= 3){ x = i; break; } } int cnt1 = 0; for (int i = 1; i <= n;i++){ if(cnt1 == 3){ break; } if(H[i] % x == 0){ cnt1++; cout << H[i] << " "; } }}
int main(){ ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); solved(); return 0;}P10429 拔河
双指针优化的题目,个人觉得比较吃技巧,想要去优化这个查找和计算答案的过程实在不好想。
题目链接:https://www.luogu.com.cn/problem/P10429
大致题意:给定个力量值为的同学,我们要从中挑选出两个队伍。满足以下条件:和,其中。两个队伍的人数不必相同,现在要使两个队伍内的力量值总和之差最小,输出能达到这个最小值。
思路:这道题给的数据是,也就是说我们最多只能使用到。(但实际上也是过不了的)最保险的还是是。但目前比较直观直接暴力枚举加前缀和预处理优化最终的复杂度也还是会有,我们必须另寻他法继续优化。
这里我参考大佬的题解用了一种类似预处理答案的做法。核心思路是在每一次组成队伍的时候就查找可能的最小值,并在过程中不断更新。循环两次,加上查找最近值,恰好能满足复杂度的要求。
我们用前缀和预处理求和,然后用一个有序集合存储第一个队伍可能的力量值总和。每一次循环,对于未在第一个队伍的同学就计算可能组成的第二个队伍的力量的总和的值。然后再在集合中二分查找找到一个最相近的值减去,更新。最后结束时,我们再更新当第一支队伍取到第个同学时,可能组成的队伍的力量值总和。
#include <bits/stdc++.h>
using namespace std;using ll = long long;#define endl '\n'#define mod 998244353
void solved(){ int n; cin >> n; vector<ll> A(n + 1); for (int i = 1; i <= n;i++){ cin >> A[i]; } if(n == 1){ cout << 0 << '\n'; return; } vector<ll> B(n + 1); for(int i = 1; i <= n;i++){ B[i] = B[i - 1] + A[i]; } multiset<ll> Lsum; Lsum.insert(A[1]); ll ans = LLONG_MAX; for (int j = 2; j <= n;j++){ for (int r = j; r <= n;r++){ ll sumr = B[r] - B[j - 1]; //j每次循环第二支队伍有n - r种可能的力量总值。 auto it = Lsum.lower_bound(sumr); if(it != Lsum.end()){ //第一个大于等于sumr的判断一次 ans = min(ans, abs(sumr - *it)); } if(it != Lsum.begin()){ //第一个小于sumr的判断一次 --it; ans = min(ans, abs(sumr - *it)); } } for (int l = 1; l <= j;l++){ //取到下一个新同学后,更新添加suml里的元素 ll suml = B[j] - B[l - 1]; Lsum.insert(suml); } } cout << ans;}
int main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); solved(); return 0;}P12349 翻转硬币
比较难的一道,对于我这种蒟蒻备赛来说其实没有什么补题的必要了。()不过还是贴一下我学习时的代码吧,详细解析可以参考一下洛谷该题题解的第一个,再加硬啃一下。(我就是这样)
题目链接:https://www.luogu.com.cn/problem/P12349
参考题解:https://www.luogu.com.cn/problem/solution/P12349
大致题意:给定行列的硬币,每一个硬币的价值取决于上下左右相邻的硬币中与其正反相同的硬币数之和的平方。我们可以进行任意次操作让任意一行的硬币翻转。求硬币的价值之和最大可能是多少。
#include <bits/stdc++.h>
using namespace std;typedef long long ll;#define endl '\n'
int n, m;int dp[1010][2]; //1.第i行。2.是否和原来相反(0为相同,1为不同)int a[1010][1010]; //dp[i][{0,1}]为前i - 1行中第i行与第i - 1行的贡献char c[1010][1010];
//calc,p1为第x行和第x-1行的状态{0,1},p2为第x行和第x+1行的状态{0,1}int calc(int x, int p1,int p2){ int sum = 0; for (int j = 1; j <= m; j++){ int cnt = 0; if(x > 1){ cnt += ((a[x][j] ^ p1) == a[x - 1][j]); } if(x < n){ cnt += ((a[x][j] ^ p2) == a[x + 1][j]); } if(j > 1){ cnt += (a[x][j] == a[x][j - 1]); } if(j < m){ cnt += (a[x][j] == a[x][j + 1]); } sum += cnt * cnt; } return sum;}
void solved(){ cin >> n >> m; for (int i = 1; i <= n;i++){ for (int j = 1; j <= m;j++){ cin >> c[i][j]; } } for (int i = 1; i <= n;i++){ for (int j = 1; j <= m;j++){ a[i][j] = c[i][j] - '0'; } } dp[2][0] = calc(1, 0, 0); dp[2][1] = calc(1, 1, 1); for (int i = 3; i <= n + 1;i++){ dp[i][0] = max(dp[i - 1][0] + calc(i - 1, 0, 0), dp[i - 1][1] + calc(i - 1, 1, 0)); dp[i][1] = max(dp[i - 1][0] + calc(i - 1, 0, 1), dp[i - 1][1] + calc(i - 1, 1, 1)); } cout << max(dp[n + 1][0], dp[n + 1][1]);}
int main(){ ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); solved(); return 0;}