大一寒假集训2020.1.5
二分法
首先,二分中最基础的二分查找。
一个非常神奇的算法/思想:
二分,分的是答案,直接在答案在的区间范围中二分,分出一个值,就判断是不是答案,并进行转移
如果已知候选答案的范围(min,max)(单调有序),有时候我们不必通过计算得到答案,只需在此范围内应用“二分”的过程,逐渐靠近答案(最后,得到答案)!
通过二分的方法,大幅度地跳过一片没必要的比较和选择
在C++中有两个函数,可以直接找出大于等于要找数下标。
upper_bound()与lower_bound()使用方法
都是二分函数,头文件 “algorithm” 也可以用 “bits/stdc++.h”
upper_bound返回第一个大于的元素的下标;
lower_bound返回第一个大于等于元素的下标;
int point[10] = {1,3,7,7,9};
int tmp = upper_bound(point, point + 5, 7) - point;//按从小到大,7最多能插入数组point的哪个位置
printf("%d\n",tmp);
tmp = lower_bound(point, point + 5, 7) - point;////按从小到大,7最少能插入数组point的哪个位置
printf("%d\n",tmp);
二分查找
直接使用刚提到的函数一行解决。
#include<bits/stdc++.h>
using namespace std;
int main()
{
int x,n;
while(cin>>n>>x)
{
int a[1000005];
for(int i=0;i<n;i++)
{
cin>>a[i];
}
int ans;
ans=upper_bound(a,a+n,x)-a;
cout<<ans<<endl;
}
return 0;
}
看起来还是不难的,注意看题,找到返回NO,未找到返回YES
#include <bits/stdc++.h>
using namespace std;
int n, q;
int a[1000005];
int erfen(int a[], int left, int right, int x)
{
int mid;
while (left < right)
{
mid = (left + right) / 2;
if (a[mid] == x)
{
return mid;
}
else if (a[mid] < x)
{
left = mid + 1;
}
else
{
right = mid;
}
}
return 0;
}
int main()
{
while (scanf("%d%d", &n, &q) != EOF)
{
int t;
for (int i = 0; i < n; i++)
{
scanf("%d", &a[i]);
}
for (int i = 0; i < q; i++)
{
scanf("%d", &t);
if (t < a[0] || t > a[n - 1])
{
printf("YES\n");
}
else if (erfen(a, 0, n - 1, t))
{
printf("no\n");
}
else
{
printf("YES\n");
}
}
}
}
坐标是实数,注意输入输出格式,还有二分的条件写法就可以了。
#include <bits/stdc++.h>
using namespace std;
double f(double x)
{
return 0.0001 * x * x * x * x * x + 0.003 * x * x * x + 0.5 * x - 3;
}
double find1(double l, double r, double y)
{
double mid = (l + r) / 2;
while (r - l >= 1e-6)
{
if (f(mid) > y)
{
r = mid;
}
else
{
l = mid;
}
mid = (l + r) / 2;
}
return mid;
}
int main()
{
double y;
double p;
while (scanf("%lf", &y) != EOF)
{
p = find1(-20, 20, y);
printf("%.4f\n", p);
}
return 0;
}
能用桶排干嘛要用二分呢
#include <bits/stdc++.h>
using namespace std;
int tong[100005];
int main()
{
int n;
cin>>n;
while (n--)
{
memset(tong, 0, sizeof(tong));
int ans = 0;
int t;
scanf("%d", &t);
while (t)
{
tong[t]++;
scanf("%d", &t);
}
for (int i = 1; i <= 50000; i++)
{
if (tong[i] != 0 && tong[2 * i] != 0)
{
ans++;
}
}
cout << ans << endl;
}
return 0;
}
把两种体积都表示出来,在给定区间内不断二分,找出要求的值。
#include <bits/stdc++.h>
using namespace std;
const double PI = acos(-1.0);
double yz(double r, int h)
{
return PI * r * r * h;
}
double v1(double r, int h)
{
return r * h;
}
int h;
double find2(double l, double r, double h)
{
double mid = (l + r) / 2.0;
while (r - l > 1e-8)
{
if (yz(mid, h) - v1(mid, h) <= pow(mid, PI))
{
r = mid;
}
else
{
l = mid;
}
mid = (l + r) / 2.0;
}
return mid;
}
double a[1000005];
int main()
{
int t;
memset(a, 0, sizeof(a));
for (int i = 1; i <= 100000; i++)
{
a[i] = find2(0, 100000, i);
}
scanf("%d", &t);
while (t--)
{
scanf("%d", &h);
printf("%.4f\n", a[h]);
}
return 0;
}
用二分求最大最小值,最小最大值
重头戏来了,这是蒟蒻认为最难解决的一个问题,还好有一些规律可循。
(虽然我看不懂)
//二分check()函数总结:一些值都符合条件,也就是求最大值时,check()函数 >=k时为返回1,
//对应的答案也在check() 返回1时记录就行!
//当一些值都符合条件,也就是求最小值时,check()函数>k返回为1,
//对应的答案在=号的位置,也就是check()返回0时记录就行!
#include <bits/stdc++.h>
using namespace std;
int n, k;
int a[10005];
int check(int x)
{
int num = 0;
for (int i = 0; i < n; i++)
{
num += a[i] / x;
}
if (num >= k)
{
return 1;
}
else
{
return 0;
}
}
int main()
{
while (scanf("%d %d", &n, &k) != EOF)
{
int mx = -99999999;
for (int i = 0; i < n; i++)
{
scanf("%d", &a[i]);
if (mx < a[i])
{
mx = a[i];
}
}
int mid = 0;
int ans = 0;
int l, r;
l = 0;
r = mx;
while (l <= r) //一定要有等于;
{
mid = (l + r) / 2;
if (check(mid) == 1)
{
ans = mid;
l = mid + 1;
}
else
{
r = mid - 1;
}
}
printf("%d\n", ans);
}
return 0;
}
上一题最小值最大,这就最大值最小。那我就来了。按照上一题的注释。搞一个check函数就OK
#include <bits/stdc++.h>
using namespace std;
int a[100005];
int n, m;
int check(int mid)
{
int num = 0;
int tmp = 0, s = 0;
for (int i = 1; i <= n; i++)
{
tmp = 0;
s = s + a[i];
if (s > mid)
{
s = a[i];
tmp = 1;
num++;
}
}
if (tmp == 0)
num++;
if (num > m)
return 1;
else
return 0;
}
int main()
{
int t;
cin >> t;
while (t--)
{
scanf("%d%d", &n, &m);
memset(a, 0, sizeof(a));
int mx = -999999;
int sum=0;
for (int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
if (mx < a[i])
{
mx = a[i];
}
sum+=a[i];
}
int l, r;
r = sum;
l = mx;
int ans = 0;int mid = 0;
while (l <= r)
{
mid = (l + r) / 2;
if (check(mid) == 1)
{
l = mid + 1;
}
else
{
ans = mid;
r = mid - 1;
}
}
printf("%d\n", ans);
}
return 0;
}
没什么特别需要注意的,就注意一下实数处理的小套路
#include <bits/stdc++.h>
//实数进位问题,先转成整型;
using namespace std;
int a[100005];
int n, k;
double t;
int check(int mid)
{
long long num = 0;
for (int i = 1; i <= n; i++)
{
num += (int)(a[i] / mid);
}
if (num >= k)
{
return 1;
}
else
{
return 0;
}
}
int main()
{
while (scanf("%d%d", &n, &k) != EOF)
{
double mx = -999999;
for (int i = 1; i <= n; i++)
{
scanf("%lf", &t);
a[i] = (int)(t * 100);
if (mx < a[i])
{
mx = a[i];
}
}
int mid = 0;
int l, r, ans = 0;
r = mx;
l = 0;
while (l <= r)
{
mid = (l + r) / 2.0;
if (mid == 0)
break;
if (check(mid) == 1)
{
ans = mid;
l = mid + 1;
}
else
{
r = mid - 1;
}
}
printf("%.2f", ans / 100.00);
}
return 0;
}
难点依旧是check函数的写法,就按照之前说的方法多练习,毕竟都是熬过来的。
#include <bits/stdc++.h>
using namespace std;
int a[100005];
int m, n;
int check(int mid)
{
int num = 0;
int tmp = 0, s = 0;
for (int i = 1; i <= n; i++)
{
tmp = 0;
s = s + a[i];
if (s > mid)
{
s = a[i];
tmp = 1;
num++;
}
}
if (tmp == 0)
num++;
if (num > m)
return 1;
else
return 0;
}
int main()
{
while (scanf("%d%d", &n, &m) != EOF)
{
int mx = -9999;
int sum = 0;
for (int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
if (mx < a[i])
{
mx = a[i];
}
sum += a[i];
}
int mid = 0;
int ans = 0;
int l = mx, r = sum;
while (l <= r)
{
mid = (l + r) / 2;
if (check(mid) == 1)
{
l = mid + 1;
}
else
{
ans = mid;
r = mid - 1;
}
}
printf("%d\n", ans);
}
}
所谓的加强版也就是多个排序,而且sort都能用
#include<bits/stdc++.h>
using namespace std;
int a[2000010];
int main()
{
int x,n;
while(cin>>n>>x)
{
memset(a,0,sizeof(a));
for(int i=0;i<n;i++)
{
cin>>a[i];
}
sort(a,a+n);
int ans;
ans=upper_bound(a,a+n,x)-a;
cout<<ans<<endl;
}
return 0;
}
拖好久真的是这几天的题太恶心,好好消化一下在看就有不一样的感悟。