SMU Summer 2024 div2 1st
提示:这里可以添加本文要记录的大概内容:
一、前言
暑假第一周训练,直接九九六了,俩个专题题单共四十题,每天一场个人赛俩到三个小时,还有对应的题单课程和题解,都在前面几篇博客里了反正。就不一一放链接了,然后酌情放几题没做出来的,着重总结一下算法学习和题单的题吧。
二、算法
1.二分算法
二分算法其实我写过好几次了,但每次写相对应的题都会遇到不会的,希望这次真的是最后一次写吧。
//二分模板
#include<bits/stdc++.h>
using namespace std;
#define int long long
bool check(int x) {
//具体判断的条件
return ;
//判断是否符合
}
signed main() {
int l,r;
while (l < r) {
int mid = (l+r+1)/2;
if(check(mid))l = mid;
else r = mid - 1;
//int mid = (l+r)/2;
//if(check(mid))r = mid;
//else l = mid + 1;
}//mid是否加1取决于输出l和r的判断
//以非注释部分为例,l直接等于mid,如果不加1,直接取符合条件的偏小数,可能陷入死循环。
cout << l << endl;
//因为l是最后的符合条件的数字。
return 0;
//小tips,必须返回不然会boom,爆!
}
<1> ([CQOI2010])
扑克牌
a不是什么难题,就是一道典型题变式,拿出来看看。
题解:
你有n种牌,第i种牌的数目为ci。另外有一种特殊的牌:joker,它的数目是m。你可以用每种牌各一张来组成一套牌,也可以用一张joker和除了某一种牌以外的其他牌各一张组成1套牌。最多组成多少组牌。
x代表能否组成这么多牌,少的牌需要joker来代替。具体见代码。
代码:
#include<bits/stdc++.h>
using namespace std;
long long n,m;
long long c[55];
bool solve(long long x) {
long long ans = 0;
for (int i = 0; i < n; i++) {
if (c[i] < x)ans+=(x-c[i]);
}
if (ans <= x && ans <= m)return true;
else return false;
}
int main() {
cin >> n >> m;
long long ma = 0;
for (int i = 0; i < n; i++) {
cin >> c[i];
if (c[i] > ma)ma = c[i];
}
long long l = 0, r = 1e9;
while (l < r) {
long long mid = (l+r+1)/2;
if (solve(mid)) {
l = mid;}
else r = mid-1;
}
cout << l;
return 0;
}
<2> (USACO 2017 Dec P)
Greedy Gift Takers
缕了好久才缕清楚这道题,然后做不出来,看题解也可看了好久才看懂,啧。
题解:
n头奶牛,每只奶牛接受礼物后会插到c【i】只牛之前,计算有多少只牛永远接受不到礼物。
找到最后一只接收到礼物的牛,重点在check里面但我有点忘记了dbq。
代码:
#include<bits/stdc++.h>
using namespace std;
int n;
const int N = 100005;
int c[N];
int b[N];
bool check(long long p) {
for (int i = 0; i < p-1; i++) {
b[i] = c[i];
}//它前面所有的牛都要插队
sort(b, b+p-1);
//救命忘记为什么要排序了,好像是前面会反复循环?
int num = n - p;
for (int i = 0; i < p-1; i++) {
if (b[i] <= num)num++;
//哟,插不到我前面
else return false;
}
return true;
}
int main() {
cin >> n;
for (int i = 0; i < n; i++) {
cin >> c[i];
}
long long x = 0, y = n+1;
long long mid = 0;
while (x < y) {
mid = (x+y+1)/2;
if (check(mid)) {
x = mid;}
else y = mid-1;
// cout << x << ' ' << y << endl;
}
cout << n - x << endl;
//让我看看有多少牛就是拿不到礼物
}
<3> (Round2 C)
Minimum Width
当时一道有点印象的二分题,做出来还挺高兴的
题解:
n个长度为l【i】的单词,每个单词间隔一空,行首不用间隔,给定最多m行,求窗口最小宽度。
二分并判断即可。
代码:
#include<bits/stdc++.h>
using namespace std;
int n,m;
long long L[200005];
long long sum;
bool solve(long long x) {
long long t = 0;
long long ls = 0;
for (int i = 0; i < n; i++) {
if (t == 0) ls++;
t += L[i];
if (t < x) {
t++;
}
else if (t == x) {
t = 0;
}
else if (t > x) {
t = L[i]+1;
//t直接定在下一个单词开头
ls++;
}
}
if (ls <= m)return true;
else return false;
}
int main() {
cin >> n >> m;
sum = n;
long long ma = 0;
for (int i = 0; i < n; i++) {
cin >> L[i];
sum += L[i];
if (L[i] > ma) ma = L[i];
}
long long l = ma,r = sum;
while (l < r) {
long long mid = (l+r)/2;
if (solve(mid)) {
r = mid;
}
else l = mid+1;
}
cout << r << endl;
return 0;
}
2.三分算法
三分跟二分类似,取俩个点即可。
老规矩写个模板
#include<bits/stdc++.h>
using namespace std;
#define int long long
signed main() {
while (r - l > 5) {
int mid1 = l + (r-l)/3;
int mid2 = r - (r-l)/3;
if (p(mid1) < p(mid2)) {
l = mid1;
}
else r = mid2;
}
int ans = 0;
for (int i = l ; i <= r; i++) {
ans = max(ans,p(i));
}
//这一段很有意思啊,因为直接大于1的话会超时,所以留下几个直接遍历
cout << ans << endl;
}
return 0;
}
for (int i = 0; i <= 300; i++){
int mid1 = l+(r-l)/3;
int mid2 = r-(r-l)/3;
ans = min(ans,check(mid1));
ans = min(ans,check(mid2));
if(check(mid1) > check(mid2))l = mid1;
else r = mid2;
}
<1> (热身1 B)
Linear Approximation
去偷俩题三分题先。
题解:
给出一个包含n个数字的数组a,求b为多少时伤心值最小。
三分思想很简单,l从-1e9,r从1e9,一直取俩点并舍去不合适区间,直到剩下一些些的时候直接便利即可,只有五十分,不知道错哪了,具体见注释。
数学思维因为取点最后一定是在俩点中间,所以取最中点与取中间任意一点相等,直接取存在的中点即可。
代码:
//已AC
#include<iostream>
#include<algorithm>
#include<math.h>
#include<cmath>
#include<queue>
#include<stack>
using namespace std;
#define int long long
int n;
int a[200005];
int check(int x) {
int res = 0;
for(int i = 1; i <= n; i++) {
res += abs(a[i]-x);
}
return res;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
a[i] = a[i] - i;
}sort(a+1,a+n+1);
int l = -1e12, r = 1e12+10;
int ans = 4e18;
for (int i = 0; i <= 300; i++){
int mid1 = l+(r-l)/3;
int mid2 = r-(r-l)/3;
ans = min(ans,check(mid1));
ans = min(ans,check(mid2));
if(check(mid1) > check(mid2))l = mid1;
else r = mid2;
//如果写l=mid1-1,r=mid2+1,就是14/15,因为r-l=3,l=2,可能一直错过中间点
//在这中间反复横跳,啊举了例子但是忘记了
}
// for (int i = l; i <= r; i++) {
// ans = min(ans,check(i));
// }//顺手再检验一下
cout << ans << endl;
//不理解十足的不理解,让我再学学再来改
//我来了!我懂了!我真的懂了!
return 0;
}
<2> (热身1 D)
Equal Cut
题解:
给定含n个数字的数组a,切三刀变成三个数组的和分别是p,q,r,s,求最大值与最小值差值的最小值。
先中间切一刀,开始遍历这个中间数,然后左右分别切一刀,希望切下来的俩块使差值尽可能小。然后每个中间数的最小值比较即可。用了一些前缀和思想,方便运算。
代码:
#include<iostream>
#include<algorithm>
#include<math.h>
#include<cmath>
#include<queue>
#include<stack>
using namespace std;
#define int long long
int n;
int a[200008];
int qz[200008];
bool check(int x,int y, int aaa) {
if(abs((x+a[aaa]) - (y-a[aaa])) < abs(x - y)) return true;
else return false;
}//判断是否要加上aaa未知的数字a
signed main() {
cin >> n;
a[0] = 0;
qz[0] = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
qz[i] = qz[i-1] + a[i];
}
int b = 1,e = 3;
//b,e是左右俩刀
int ans = 1e9+5;
for (int i = 2; i <= n-2; i++) {
//i是中点
int p = qz[b];
int q = qz[i] - qz[b];
int r = qz[e] - qz[i];
int s = qz[n] - qz[e];
while (check(p,q,b+1) && b+1 < i) {
b++;
p = qz[b];
q = qz[i] - qz[b];
}//一直加到俩边尽可能接近
while (check(r,s,e+1) && e+1 < n) {
e++;
r = qz[e] - qz[i];
s = qz[n] - qz[e];
}
int test[10];
test[0]=p;test[1]=q;
test[2]=r;test[3]=s;
sort(test,test+4);
ans = min(ans,test[3]-test[0]);
//每次遍历的最小值都要相比
}
cout << ans << endl;
return 0;
}
三、总结
还有一个题单,写的题不太多,下次再改一下周报吧,就先写到这了。每日总结基本都在每日题解里了。