二分初体验

8 篇文章 0 订阅
7 篇文章 0 订阅

目录

P2678 跳石头

这题一开始没读懂题,后来发现实际上是个类似贪心的思想
大意是说,每种移石子的方案都会有一个最小的距离,然后要求这其中的最大距离
也就是喜闻乐见的最小值最大
暴力枚举每种方案的复杂度是O(n^m),铁定会挂
我们自然会想到枚举每种答案再判断是否合法
而二分就是一种优化枚举答案次数的算法,复杂度O(nlogn),十分优秀
具体做法是二分答案,然后判断
由于读入是单调的,就省去了排序
判断的具体过程就是看这个解是否合法,简单来说就是模拟跳石子的过程看看是否符合题意
如果合法,考虑到答案不一定最优,继续往下找
如果不合法,由于序列是单调递增的,因此当前不合法后面的一定也不合法,因此往前找
这里给出上一行的证明
不合法说明撤掉的石子m1>m才能使答案成立,如果答案更大,则需要更大的m2>=m1,所以m2显然>m,不合法

这里其实还有个贪心的思想
可以把前缀和排序,然后尽量移除最小的(类似合并果子的操作),然而复杂度是爆炸的

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

int const maxn=60000;
int a[maxn];
int d,n,m,l,r,ans;

int judge(int ans)
{
	int next=1,now=0,cnt=0;
	while(next<=n+1)
	{
		if(a[next]-a[now]<ans)
			cnt++;
		else
			now=next;
		next++;
	}
	if(cnt>m)
		return 0;
	return 1;
}
int main()
{
	scanf("%d%d%d",&d,&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	a[n+1]=d;
	l=1,r=d;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(judge(mid))
		{
			ans=mid;
			l=mid+1;
		}
		else
			r=mid-1;
	}
	printf("%d",ans);
	return 0;
}

好了我总算知道二分答案的真谛了= =
所谓二分答案其实是二分最优的满足某种性质的值(最优&&可行),然后验证他是否符合这种性质即可,比如最小值最大就是二分最大,验证是否符合最小
综上所述,能进行二分答案的条件
: 答案可以验证(比如模拟等等)
: 答案范围小(满足nlogn的复杂度)

CodeVS 2072 分配房间

这题和跳石头还不一样= =
这题必须要先选一个
因此边界处理不同

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

int const maxn=1000101;
long long n,m,l,r,a[maxn],ans;

int check(int ans)
{
	int last=1,cnt=m-1;
	for(int i=2;i<=n;i++)
	{
		if(a[i]-a[last]>=ans)
		{
			cnt--;
			last=i;
		}
	}
	if(cnt<=0)
		return 1;
	return 0;
}
int main()
{
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
	}
	sort(a+1,a+1+n);
	l=0,r=a[n]-a[1];
	//在这出了锅 
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(check(mid))
		{
			ans=mid;
			l=mid+1;
		}
		else
			r=mid-1;
	}
	printf("%lld",ans);
	return 0;
}

好了二分答案又一关键:初始化、边界问题
这个无法总结,具体题目具体分析吧

CodeVs 1725探险

这题没啥好说的,一眼就看出来怎么做了
就是边界调了好久…
还是手模靠谱

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

int const maxn=1000110;
int n,m,a[maxn],prs[maxn],l,r,ans;

inline int check(int ans)
{
	int cnt=0,last=0;
	for(int i=1;i<=n;i++)
		if(prs[i]-prs[last]>=ans)
		{
			cnt++;
			last=i;
		}
	if(cnt>=m)
		return 1;
	return 0;
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		prs[i]=a[i]+prs[i-1];
//		printf("%d\n",prs[i]);
	}
	l=0,r=prs[n];
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(check(mid))
		{
			ans=mid;
			l=mid+1;
		}
		else
			r=mid-1;
	}
	printf("%d",ans);
	return 0;
}
//又又又栽在初始化上了QAQ,以后绝对不能臆想了,还是手模靠谱... 

P1083 借教室

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;

int const maxn=1000110;
int n,m,a[maxn],dlt[maxn],x[maxn],y[maxn],l,r,c[maxn],ans;

inline int check(int h)
{
	memset(c,0,sizeof(c));//每次都要把差分数组还原 
	int sum=0;
	for(int i=1;i<=h;i++)
	{
		c[x[i]]+=dlt[i];
		c[y[i]+1]-=dlt[i];//边界问题 
	}
	for(int i=1;i<=n;i++)
	{
		sum+=c[i];
//		prs[i]=prs[i-1]+c[i];
	//我曾经是这么写的,但总感觉不对
	//好吧我蠢了
	//prs[1]=prs[0]+c[1]就相当于初始化了== 
		if(sum>a[i])
			return 0;
	}
	return 1;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	for(int i=1;i<=m;i++)
		scanf("%d%d%d",&dlt[i],&x[i],&y[i]);
	l=0,r=m;
	if(check(r))
	{
		printf("0");
		return 0;
	}
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(check(mid))
			l=mid+1;
		else
		{
			ans=mid;
			r=mid-1;
		}
	}
	printf("-1\n%d",ans);
	return 0;
}
//答案到底是mid还是mid+1傻傻分不清楚== 
//好吧是我自己的锅,ans应该在订单超出时赋值 

P1314 聪明的质检员

首先,这道题的题目叫 想要摸鱼却却又缺乏智商的质检员 ,这个质检员想要摸鱼,却不知道怎么做,于是写了道题来求诸诸佬~~(然而又不能丑化自己,所以就叫聪明的质检员)~~
这道题思路很好想
由于w越大y越小,因此符合单调性
很容易想到二分w,根据w与y的关系来判断向哪个方向二分
具体细节看代码

#include<iostream>
#include<cstdio>
typedef long long ll;
//这道题要开long long
using namespace std;

int const maxn=2000100;
ll const maxx=9000000000000000000;
//long long 的上界比1e19小一点
int n,m,x[maxn],y[maxn],w[maxn],v[maxn];
int mn=0x3f3f3f3f,mx,l,r;
ll prsv[maxn],prsn[maxn],sum,c,ans=maxx,s;

inline int check(int ans)
{
	sum=0;
	for(int i=1;i<=n;i++)
	{
		if(w[i]>=ans)
			prsv[i]=prsv[i-1]+v[i],prsn[i]=prsn[i-1]+1;
		else
			prsv[i]=prsv[i-1],prsn[i]=prsn[i-1];
	}
	//这里是利用前缀和来维护区间大于等于k的值
	for(int i=1;i<=m;i++)
		sum+=(prsv[y[i]]-prsv[x[i]-1])*(prsn[y[i]]-prsn[x[i]-1]);
	c=llabs(sum-s);
	if(sum>s)
		return 1;
	return 0;
}

int main()
{
	scanf("%d%d%lld",&n,&m,&s);
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&w[i],&v[i]);
		mx=max(w[i],mx);
		mn=min(w[i],mn);
	}
	for(int i=1;i<=m;i++)
		scanf("%d%d",&x[i],&y[i]);
	l=mn-1,r=mx+2;
	//这里是代码的精髓之处,虽然出题人并没有特意卡,但边界条件一定要想到
	//二分有个性质,可以到达左界,但无法达到右界
	//而这道题只是让选一个w,并没有指定范围
	//因此就有三种情况,在w[i]的范围内、小于和大于
	//因此mn-1指小于,mn~mx+1指在范围内,mn+2指大于
	while(l<=r)
	{
		int mid=l+r>>1;
		if(check(mid))
			l=mid+1;
		else
			r=mid-1;
		ans=min(ans,c);
		//这里因为二分的并不是最终的答案,哨兵一下即可
	}
	printf("%lld",ans);
	return 0;
}

P1525 关押罪犯

最大值最小,满足二分性质
分属的两个监狱可以看做二分图
如果满足二分图则一定可以完美分配
我们可以二分答案,将小于答案的边删去重新建图,如果能建立二分图,说明答案应该比当前答案小,如果不能建立,说明当前答案可行
PS:答案应是不可行的(即无法建二分图的),因此其实是最小值最大!

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
int const maxn=210000,maxm=1001100;
struct E
{
    int to,next,w;
    E(int to=0,int next=0,int w=0):
        to(to),next(next),w(w){}
}e[maxm<<1];
int head[maxn],vis[maxn],color[maxn];
int n,m;
int cnt,ans,maxx;
void add(int u,int v,int w)
{
    e[++cnt]=(E){v,head[u],w};
    head[u]=cnt;
}
void readin()
{
    scanf("%d%d",&n,&m);
    for(int x,y,z,i=1;i<=m;i++)
        scanf("%d%d%d",&x,&y,&z),add(x,y,z),add(y,x,z),maxx=std::max(maxx,z);
}
int check(int min)
{
    std::queue<int>q;
    memset(color,0,sizeof(color));
  	for(int i=1;i<=n;i++)
  		if(!color[i])
  		{
  			q.push(i),color[i]=1;
  			while(!q.empty())
  			{
  				int u=q.front();q.pop();
  				for(int j=head[u];j!=-1;j=e[j].next)
  				{
  					int v=e[j].to,w=e[j].w;
  					if(w<min)
  						continue;
  					if(!color[v])
  						color[v]=color[u]==1?2:1,q.push(v);
  					else
  						if(color[u]==color[v])
  							return false;
  				}
  			}
  		}
    return true;
}
int main()
{
	memset(head,-1,sizeof(head));
	readin();
    int l=0,r=maxx+1;
    while(l<=r)
    {
        int mid=l+r>>1;
        if(check(mid))
            r=mid-1;
        else
        {
            l=mid+1;
            ans=mid;
		}
    }
    printf("%d",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值