【长春大学ACM协会第二次测试】题解
本次测试为二分专题测试,考察各位对于二分的理解与运用。
题目分布:第一题为二分acwing的板子题,第二题到第四题为洛谷橙色题目,第五题和第六题分别是洛谷黄色、绿色题目
主要知识点均为二分,除了第五、六题涉及到搜索和并查集。
第一题
因为为板子题,题解省略,具体请自行查询acwing官网上算法基础课中基础算法的二分内容。
链接:https://www.acwing.com/activity/content/problem/content/823/
第二题
根据题目具体内容:由于三个解的分布间隔大于等于1,而且范围已经给出在 − 100 -100 −100 到 100 100 100 之间,因此,数据量足够小,所以只需要对 − 100 -100 −100 到 100 100 100 之间进行遍历,将这些数看做200个间隔,例如 1 − 2 1-2 1−2 之间为一个间隔,对每个间隔进行二分搜索即可。
#include<iostream>
#include<iomanip>
#include<algorithm>
#include<functional>
#include<numeric>
#include<math.h>
#include<string>
#include<vector>
#include<queue>
#include<deque>
#include<list>
#include<set>
#include<map>
#include<cstring>
using namespace std;
const int N = 1;
typedef pair<int,int> PII;
typedef long long ll;
double a,b,c,d;
double fuc(double x){
return a*x*x*x+b*x*x+c*x+d;
}
int main()
{
ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
cin>>a>>b>>c>>d;
int cmd=0;
for(double i=-100;i<=100;i++){
if(fuc(i)*fuc(i+1)<=0){
if(fuc(i)==0){
cout<<fixed<<setprecision(2)<<i<<' ';
cmd++;
}
else if(fuc(i+1)==0){
cout<<fixed<<setprecision(2)<<i+1<<' ';
i++;
cmd++;
}
else {
double l=i,r=i+1;
double mid;
while(l<=r&&abs(l-r)>=1e-3){
mid=(l+r)/2;
if(fuc(mid)*fuc(l)<=0)r=mid;
else l=mid;
}
cout<<fixed<<setprecision(2)<<l<<' ';
cmd++;
}
}
if(cmd>=3)return 0;
}
}
注: 第三题和第四题都有简单的数学解法,因为本次考察的是对二分的运行,简单的数学解法在此处并不给出。
第三题
第三题需要我们找到一个最少的血量,保障勇士在闯关的过程中血量不会等于小于0,那么我们直接在一个大区间进行二分搜索,当血量满足成功闯关的条件时,则向左边找,当不满足时向右边找。
#include<bits/stdc++.h>
using namespace std;
int n,a[100005];
bool check(int mid)
{
int xue=mid;//假设血量
for(int i=1; i<=n; i++)
{
xue+=a[i];//血量加减
if(xue<=0)//判断机器猫会不会死
return false;//会死,返回假,让主函数中假设的血量变多
}
return true;//不然让主函数中假设的血量变少
}
int main(){
cin>>n;
for(int i=1; i<=n; i++)
cin>>a[i];
int l=1,r=100000000;
while(l<r)//二分查找
{
int mid=(l+r)/2;//假设血量
if(check(mid))//上面已经说过了
r=mid;
else
l=mid+1;
}//上边是模版,自己记住
cout<<l;//输出答案
return 0;
}
第四题
同第三题,也是在一个大的区间内找寻符合条件的答案。如果买的数量使得最后的结果大于等于我们需要的结果时,向左边搜索,找最优解,当不满足时向右找。
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 9;
int n;
int main() {
cin >> n;
int l = 1, r = n;
auto check = [](int x) {
int cnt = x, ans = x;
while (cnt >= 3) {
int q = cnt / 3;
ans += q;
cnt = q + cnt % 3;
}
return ans;
};
while (l < r) {
int mid = l + r >> 1;
if (check(mid) >= n) r = mid;
else l = mid + 1;
}
cout << r << "\n";
}
第五题
这道题涉及到并查集,也可以看做是并查集的板子题,在这里先说并查集的思路。
并查集:先对结点按照时间正序排序,并且根据时间逐步的将每个对应的结点连接,直到所有的结点均链接到一起,此时输出当前的时间。
二分:从 0 0 0 到最大时间(即最后一条公路修复完毕的时间)进行二分搜索,并且check当前时间是否所有的村庄均连接,如果未连接则向右搜索,否则向左搜索,直到找到最后的mid值,并且向右取保障全部的村庄均连接的时间(向右取根据个人代码情况,只要是找到一个时间让所有的村庄刚刚连接完毕即可)。
二分代码:
void dfs(int u)
{
dist[u]=1;
for(auto x:g[u])
{
if(dist[x]==1) continue;
dfs(x);
}
}
bool check(int t)
{
//cout<<t<<endl;
for(int i=0;i<=n;i++) {
dist[i]=0;
g[i].clear();
}
for(int i=1;i<=m;i++)
{
if(tr[i].time>t) break;
int x=tr[i].x,y=tr[i].y;
g[x].push_back(y);
g[y].push_back(x);
}
dfs(1);
for(int i=1;i<=n;i++)
if(dist[i]==0)
return false;
return true;
}
void ik_solve()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int x,y,t;
cin>>x>>y>>t;
tr[i]={x,y,t};
}
sort(tr+1,tr+1+m,cmp);
int l=0,r=1e6+10;
while(l<r)
{
int mid=l+r >>1;
if(check(mid)) r=mid;
else l=mid+1;
}
// cout<<l<<endl;
if(!check(l)) cout<<-1;
else
cout<<l<<endl;
}
signed mian() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
T=1;
// cin>>T;
while(T--)
{
ik_solve();
}
return 0;
}
第六题
此题可以用并查集+二分也可以用广搜+二分,在这里给出广搜+二分的思路。
二分的范围可以从 0 0 0 到最大值(该最大值为高度最小的结点和高度最大的结点之间的绝对值差),在这个范围内进行搜索,如果所有的路标都可以在一个 D D D 的值被连接,则向左搜索,反之向右搜索。广搜内容直接套广搜的板子,能够被连接的条件就是两者之间的高度差小于等于 D D D ,同理这也是可以被加入广搜 q u e u e queue queue 的条件。
#include <bits/stdc++.h>
using namespace std;
bool lb[505][505],pd=1,p[505][505];
int n,m,a[505][505],l,r,mid,ans,st,en,tp;
int dx[4]={0,1,0,-1};
int dy[4]={1,0,-1,0};
inline bool bfs() { //直接套广搜板子
queue <int> x, y;
int sum=1;
x.push(st);
y.push(en);
p[st][en]=1;
while(!x.empty ()) {
int xx=x.front(),yy=y.front();
if(sum==tp) return 1; //所有路标都被包含了就可以返回true了
x.pop();
y.pop();
for(register int i=0;i<4;i++) {
int xxx=xx+dx[i];
int yyy=yy+dy[i];
if(xxx<1||xxx>n||yyy<1||yyy>m||p[xxx][yyy]||abs(a[xxx][yyy]-a[xx][yy])>mid) continue;
if(lb[xxx][yyy]==1) sum++; //统计覆盖到的路标
x.push(xxx); //入队
y.push(yyy);
p[xxx][yyy]=1;
}
}
return 0;
}
int main () {
scanf("%d%d",&n,&m);
for(register int i=1;i<=n;i++) {
for(register int j=1;j<=m;j++) {
scanf("%d",&a[i][j]);
r=max(r,a[i][j]);
}
}
for(register int i=1;i<=n;i++) {
for(register int j=1;j<=m;j++) {
scanf("%d",&lb[i][j]);
if(lb[i][j]==1) tp++;
if(pd==1&&lb[i][j]==1) { //标记第一个路标
st=i;
en=j;
pd=0;
}
}
}
while(l<=r) {
mid=(l+r)>>1;
memset(p,0,sizeof p);
if(bfs()==true) {
r=mid-1;
ans=mid;
}
else l=mid+1;
}
printf("%d",ans);
return 0;
}