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

posted @ 2022-08-14 13:32  樱雪喵  阅读(143)  评论(0编辑  收藏  举报