二分答案(持续更新)

二分答案(洛谷、codeforces)


P2678 [NOIP2015 提高组] 跳石头

跳石头

一道经典的二分答案题,二分分为二分查找答案和把答案进行二分,二分答案经常含有最小值最大等字眼。
(泪目,好半天才理解透彻这一题)

#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int l, n, m; //储存终点,总石头个数,移走石头个数
int a[N];
bool half(int x) //判断期望的距离是否满足的函数
{
	int s = 0, num = 0; // s为当前人所在位置
	for (int i = 1; i <= n; i++)
	{
		if (a[i] - s < x)
			num++; //如果下一个石头的位置与现在所在位置的距离没有超过期望距离,就移走石头
		else
			s = a[i]; //否则就认为下一步跳到了当前所在位置
	}
	if (num > m)
		return 0; //期望的距离太大了,移走的石头过多,这时候要缩短期望距离
	else
		return 1; //移走的石头在可承受范围内
}
int main()
{
	cin >> l >> n >> m;
	for (int i = 1; i <= n; i++)
		cin >> a[i];
	int ll = 0, rr = l; //起点和终点
	int ans = 0;
	while (ll <= rr)
	{
		int mid = (ll + rr) / 2;
		if (half(mid))
		{				  //移走的石头数量在可承受范围内不代表就是最优答案,应在最右端查找最优解
			ll = mid + 1; //在右端查找,相当于增大期望距离
			ans = mid;	  //更新答案
		}
		else
			rr = mid - 1; //左端查找,缩小期望距离
	}
	cout << ans << endl;
	return 0;
}

P1902 刺杀大使

刺杀大使

看到这一题的第一反应是二分找最优解和dfs迷宫找出口的一条适合解的路。但是没想到Debug了一下午,最后还T了,泪目(T-T)。这一题看数据1000过大,既然T了执意要用dfs的话肯定要进行剪枝,这里看到大佬的题解的精妙剪枝,在每次进行dfs之前就用一个数组把能够走的全部筛出来,用0表示能够走,另外用一个数组表示是否经过这个位置。

dfs做法:

#include <iostream>
#include <cstring>
using namespace std;
const int N = 2100;
int n, m,flag;
int a[N][N], b[N][N],c[N][N];
int d[4][2] = {{1, 0}, {-1, 0}, {0, -1}, {0, 1}};//走上下左右四个方向

void dfs(int x, int tx, int ty)
{
	if (tx == n){//dfs退出标志
		flag=1;
		return;
	}
	for (int i = 0; i < 4; i++)
	{
	int xx =tx +d[i][0];
	int yy =ty +d[i][1];
		if (xx > 0 && yy > 0 && xx <= n && yy <= m ){
		if(!b[xx][yy]&&!c[xx][yy])
		{
			b[xx][yy] = 1;
			dfs(x, xx, yy);
			if(flag)break;//找到路径不用继续查找
		}
		}
	}
}
int main()
{	
	
	scanf("%d %d",&n,&m);
	int ll = 0, rr = 0;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
		{
			scanf("%d",&a[i][j]);
			rr = max(rr, a[i][j]);
		}
	
	while (ll< rr)//二分经典模板
	{
		flag=0;
		memset(b,0,sizeof(b));//养成好习惯,记得每次清空
		int mid = (ll + rr) >>1;
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++){
				if(a[i][j]<=mid)c[i][j]=0;
				else c[i][j]=1;
			}
		dfs(mid,1,1);
		if (flag)//左边界与右边界一定要想清楚
			rr = mid;
		else
			ll = mid+1;
	}
	printf("%d\n",rr);
	return 0;
}

既然数据过大,路径会很多,那不妨来试一试bfs。bfs就好像流水一样,每一次逐层的进行染色。

bfs做法:

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

const int MaxN = 1005;
const int inf = 0x3f3f3f3f;
int p[MaxN][MaxN], vis[MaxN][MaxN];
int n, m;
int l = inf, r = -inf, mid, ans, f;
const int d[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};

bool bfs(int x, int y, int maxn) //判断mid是否可行的bfs
{
	queue<pair<int, int>> q;
	q.push(make_pair(x, y));
	vis[x][y] = 1;
	while (q.size())
	{ //以下就是bfs板子
		int xx = q.front().first;
		int yy = q.front().second;
		q.pop();
		for (int i = 1; i <= 4; i++)
		{
			int nx = xx + d[i][0];
			int ny = yy + d[i][1];
			if (nx < 1 || nx > n || yy < 1 || yy > m || vis[nx][ny] || p[nx][ny] > maxn)
				continue; //越界
			vis[nx][ny] = 1;
			if (nx == n)
				return 1; //出口
			else
				q.push(make_pair(nx, ny));
		}
	}
	return 0;
}

int main()
{
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			scanf("%d", &p[i][j]);
			r = max(r, p[i][j]);
			l = min(l, p[i][j]);
		}
	}
	while (l <= r)
	{ //二分答案模板
		mid = (l + r) >> 1;
		f = 0;
		memset(vis, 0, sizeof(vis)); //重置数组
		if (bfs(1, 1, mid))
			r = mid - 1, ans = mid; //如果这个mid可行,说明可能还能再小,于是更新答案 + 缩小范围
		else
			l = mid + 1; // mid此不可行,说明不可能再小,也缩小范围,不更新答案
	}
	printf("%d", ans);
	return 0;
}

C. Balanced Stone Heaps

C. Balanced Stone Heaps

看题最小的一堆石头最大,典型的二分答案的题,但是要怎样进行判断答案是否是符合的呢?将所有石头进行从后往前进行遍历,如果有多的石头就将石头分成三份(因为多余部分只能是0或者3的倍数会给出),一份给相邻的,另一份给隔一个相邻的。直到最终将所有的石头堆遍历完,最后记得判断第一第二堆是否也满足,因为前面没有石头堆了。

#include <iostream>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
int a[N], b[N];
int n;

bool check(int x)//判断
{
	for (int i = 0; i < n; i++)
		b[i] = a[i];
	for (int i = n - 1; i > 1; i--)
	{
		if (b[i] >= x)
		{
			int d = min((b[i] - x) / 3, a[i] / 3);
			b[i - 1] += d;
			b[i - 2] += 2 * d;
		}
		else
			return 0;
	}
	return (b[0] >= x && b[1] >= x);
}
int main()
{
	int T;
	cin >> T;
	while (T--)
	{
		cin >> n;
		int ll = 0, rr = 0;
		for (int i = 0; i < n; i++)
		{
			cin >> a[i];
			rr = max(rr, a[i]);
		}
		while (ll < rr)//二分
		{
			int mid = (ll + rr + 1) >> 1;
			if (check(mid))
				ll = mid;
			else
				rr = mid - 1;
		}
		cout << ll << endl;
	}
	return 0;
}

B. A Trivial Problem

B. A Trivial Problem

思路:使得每个数的阶乘结尾有n个0,可以想到只有当2*5时结果的末尾是0。一个数的阶乘最终可以简化成几个数的幂次的形式,而且2的个数一定比5的个数大,那么只用考虑5的个数。5,10,15,20…分别对应有1,2,3,4…个5,那么规律就可以找出来了。但是需要注意的是,像25这样的特殊情况就有6个5没有按照规律来,需要筛出去。

本题数据不大,可以用暴力解决。
暴力解题:

#include <iostream>
using namespace std;
int main()
{
	int n;
	cin >> n;
	long long num = 0;
	for (int i = 5; i <= 500000;i+=5){
		long long t = i;
		while(t){//记录5的个数,防止像25的情况
			if(t%5==0){
				t /= 5;
				num++;
			}
			else
				break;
		}
		if(num>=n){
			if(num==n){
				cout << 5 << endl;//一定只会有5个这样的数
				for (int j = 0; j < 5;j++)
					cout << i++ << " ";
				cout << endl;
				break;
			}
			else{
				cout << "0" << endl;
				break;
			}
		 }
	}
		return 0;
}

二分答案:

#include<iostream>
using namespace std;
int count(int m)//记录5的个数
{
    int ans=0;
    while(m)
        ans+=m/5,m/=5;
    return ans;
}
int Solve(int m)
{
    int l=1,r=500000,mid,ans=0;
    while(l<=r)//经典二分模板
    {
        mid=(l+r)>>1;
        if(count(mid)>=m)ans=mid,r=mid-1;
        else l=mid+1;
    }
    if(count(ans)==m)return ans;
    return -1;
}
int main()
{
    int m;
	cin >> m;
	while(1)
    {
        int ans=Solve(m);
        if(ans==-1)
			cout << 0 << endl;
		else
        {
			cout << 5 << endl;
			for(int i=0;i<5;i++)
				cout << ans + i << " ";
			cout << endl;
		}
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值