YbtOJ 「基础算法」第3章 二分算法
例题1.数列分段
二分每段和的最大值。check 时从左往右扫,如果当前段的和大于限制则新开一段。
code
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,m,a[N];
int maxn,s;
int check(int x)
{
int cnt=1,sum=0;
for(int i=1;i<=n;i++)
{
if(sum+a[i]>x) cnt++,sum=a[i];
else sum+=a[i];
}
//cout<<x<<" "<<cnt<<endl;
return cnt;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),maxn=max(maxn,a[i]),s+=a[i];
int l=maxn,r=s;
while(l<r)
{
int mid=(l+r)>>1;
if(check(mid)<=m) r=mid;
else l=mid+1;
}
cout<<l<<endl;
return 0;
}
/*
6 3
4 2 4 5 1 1
*/
例题2.防具布置
因为中间只有一个奇数,考虑求前缀和。则该奇数位以前的前缀和都是偶数,以后的都是奇数。二分这个分界点即可,注意判无解。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+5;
int T,n,s[N],e[N],d[N];
int check(int x)
{
int sum=0;
for(int i=1;i<=n;i++)
{
if(s[i]>x) continue;
sum+=(min(e[i],x)-s[i])/d[i]+1;
}
return sum;
}
signed main()
{
scanf("%lld",&T);
while(T--)
{
scanf("%lld",&n);
for(int i=1;i<=n;i++) scanf("%lld%lld%lld",&s[i],&e[i],&d[i]);
long long l=1,r=(long long)(1ll<<31ll)-1ll;
if((check(r)&1)==0) {cout<<"There's no weakness."<<endl;continue;}
//cout<<l<<" "<<r<<endl;
while(l<r)
{
int mid=(l+r)>>1;
//cout<<mid<<" "<<check(mid)<<endl;
if((check(mid)&1)==0) l=mid+1;
else r=mid;
}
cout<<l<<" "<<check(l)-check(l-1)<<endl;
}
return 0;
}
例题3.最大均值
实数域上二分平均值。
check 时将区间求和转化为前缀和的形式,则对于每一个 \(i\),找到 \([1,i-l+1]\) 中最小的 \(j\) 并逐一判断能否满足条件。
\(j\) 的区间每次加 1,无需循环,开变量记录当前最小值即可。
code
#include<bits/stdc++.h>
using namespace std;
typedef double db;
const int N=1e5+5;
const db eps=1e-5;
int n,l,a[N],maxn;
db b[N];
bool check(db x)
{
db minn=1e9;
for(int i=1;i<=n;i++) b[i]=1.0*a[i]-x;
//for(int i=1;i<=n;i++) cout<<b[i]<<" ";
//cout<<endl;
for(int i=1;i<=n;i++) b[i]=b[i-1]+b[i];
for(int i=l;i<=n;i++)
{
minn=min(minn,b[i-l]);
//cout<<i<<" "<<b[i]<<" "<<minn<<endl;
if(b[i]-minn>=-eps) {return 1;}
}
return 0;
}
int main()
{
scanf("%d%d",&n,&l);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),maxn=max(maxn,a[i]);
db l=-1e9,r=maxn;
while(r-l>eps)
{
db mid=(l+r)/2.0;
if(check(mid)) l=mid;
else r=mid;
}
printf("%.0lf\n",l*1000.0);
return 0;
}
1.喂养宠物
二分养的兔子数量,求出对应每个兔子需要的食物数量。
将兔子按需要食物多少排序,从小往大买兔子,根据需要食物总和判断方案是否合法。
code
#include<bits/stdc++.h>
using namespace std;
const int N=55;
int n,t,h[N],g[N],c[N];
int minn=2e9;
int check(int x)
{
for(int i=1;i<=n;i++) c[i]=h[i]+g[i]*(x-1);
sort(c+1,c+n+1);
int sum=0;
for(int i=1;i<=x;i++) sum+=c[i];
return sum;
}
int main()
{
scanf("%d%d",&n,&t);
for(int i=1;i<=n;i++) scanf("%d",&h[i]),minn=min(minn,h[i]);
for(int i=1;i<=n;i++) scanf("%d",&g[i]);
if(minn>t) {cout<<"0"<<endl;return 0;}
int l=1,r=n;
while(l<r)
{
int mid=(l+r+1)>>1;
if(check(mid)<=t) l=mid;
else r=mid-1;
}
cout<<l<<endl;
return 0;
}
2.最小时间
两种情况:
- 答案单调递减:判断 \(0\) 是否可行,因为数据保证有解,所以不可行就一定是第二种情况。
- 答案单调递增:二分答案,从大到小排序取数。使用
nth_element
把复杂度降低一个 log。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
int n,m,s,k[N],b[N],c[N];
bool check(int x)
{
for(int i=1;i<=n;i++) c[i]=k[i]*x+b[i];
nth_element(c+1,c+m,c+n+1,greater<int>());
int sum=0;
for(int i=1;i<=m;i++)
{
if(c[i]<0)continue;
sum+=c[i];
if(sum>=s) return 1;
}
return 0;
}
signed main()
{
scanf("%lld%lld%lld",&n,&m,&s);
for(int i=1;i<=n;i++) scanf("%lld%lld",&k[i],&b[i]);
int l=1,r=1e9;
if(check(0)) {cout<<"0"<<endl;return 0;}
while(l<r)
{
int mid=(l+r)>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
cout<<l<<endl;
return 0;
}
3.攻击法坛
二分 \(L\) 的值,进行 dp。
设 \(g_i\) 表示在第 \(i\) 个法坛使用第一个法杖最远能够破坏掉第 \(g_i\) 个法坛。\(h_i\) 表示第二个法杖。
以上两个数组可以用类似双指针的方式 \(O(n)\) 求出。
4.跳石头
二分最短跳跃距离,把所有距离小于 mid 的石头移走,统计移走石头数。
code
#include<bits/stdc++.h>
using namespace std;
int s,n,m,a[500005];
int check(int x)
{
int now=0,ans=0;
for(int i=1;i<=n+1;i++)
{
if(a[i]-a[now]<x) ans++;
else now=i;
}
return ans;
}
int main()
{
scanf("%d%d%d",&s,&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
a[n+1]=s;
int l=1,r=s,mid;
while(l<r)
{
mid=(l+r+1)>>1;
if(check(mid)>m) r=mid-1;
else l=mid;
}
cout<<r<<endl;
return 0;
}
5.飞离地球
书上那个代码实在看不明白有大佬教教窝嘛/wq
提醒一下,速度控制器是要么全加要么全减,不是每条边单独选择加或减。(题意不清差评
二分速度控制器的值。用 spfa 判断有无负环。注意,如果存在一个负环但它不在 1 到 n 的路径上,它不会造成答案不合法。
因此先正反建图 dfs 出每个点能否从 1 到达及能否到达 n。而不满足条件的点在 spfa 时直接跳过就好了。
多测要清空数组。二分时多次 spfa 也一定要清空数组。
code
#include<bits/stdc++.h>
using namespace std;
const int N=105;
int T,n,m;
int head[N],cnt,head1[N],cnt1;
struct node{
int nxt,to,w;
}e[N*N],e1[N*N];
void add(int u,int v,int w){
e[++cnt].nxt=head[u];e[cnt].to=v;e[cnt].w=w;head[u]=cnt;
}
void add1(int u,int v,int w){
e1[++cnt1].nxt=head1[u];e1[cnt1].to=v;e1[cnt1].w=w;head1[u]=cnt1;
}
bool st[N],ed[N];
void dfsz(int x)//正图dfs
{
st[x]=1;
for(int i=head[x];i;i=e[i].nxt)
{
int v=e[i].to;
if(!st[v]) dfsz(v);
}
}
void dfsf(int x)//反图dfs
{
ed[x]=1;
for(int i=head1[x];i;i=e1[i].nxt)
{
int v=e1[i].to;
if(!ed[v]) dfsf(v);
}
}
bool vis[N];
int tot[N],dis[N];
int spfa(int s,int mid)
{
queue<int> q;
for(int i=1;i<=n;i++) dis[i]=2e9,tot[i]=0,vis[i]=0;
q.push(s);
dis[s]=0;vis[s]=1;
while(!q.empty())
{
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(!st[v]||!ed[v]) continue;
if(dis[v]>dis[u]+e[i].w+mid)
{
dis[v]=dis[u]+e[i].w+mid;
if(!vis[v])
{
if(++tot[v]>=n) return -1;
vis[v]=1;q.push(v);
}
}
}
}
if(dis[n]<0) return -1;
else return dis[n];
}
bool check(int mid)//然而并没有什么用的 check
{
if(spfa(1,mid)==-1) return 0;
else return 1;
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) st[i]=ed[i]=vis[i]=head[i]=head1[i]=0;
cnt=cnt1=0;
for(int i=1,u,v,w;i<=m;i++)
{
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);add1(v,u,w);
}
dfsz(1);dfsf(n);
for(int i=1;i<=n;i++)
{
if(!st[i]||!ed[i]) head[i]=0;
}
if(!st[n]) {cout<<"-1"<<endl;continue;}
int l=-1e7,r=1e7;
while(l<r)
{
int mid=(l+r)>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
cout<<spfa(1,l)<<endl;
//cout<<check(-1)<<endl;
}
return 0;
}
突然发现代码怎么粘贴进来就变成八格缩进了啊...看着好奇怪,有什么解决方法吗/kel
本文来自博客园,作者:樱雪喵,转载请注明原文链接:https://www.cnblogs.com/ying-xue/p/16584995.html