二分专题训练

[USACO 2009 Dec S]Music Notes

这道题,我刚开始是想用二分去做,不过当时还不太会,结果错了,然后我直接用floor去找他的上下界,结果过了,代码是这样的:

#include<stdio.h>
#include<math.h>
int main()
{
    int n;
    scanf("%d", &n);
    for(int i=0;i<n;i++)
    {
        int a, b;
        scanf("%d%d", &a, &b);
        int l=floor(sqrt(a));
        int r=floor(sqrt(b));
        int t=r-l;
        if((int)sqrt(a)*(int)sqrt(a)==a)
            t++;
        printf("%d\n", t);
    }
}

不过后面搞懂二分之后,又用二分做了一遍,代码如下:

#include<stdio.h>
#include<math.h>
int main()
{
    int n;
    scanf("%d", &n);
    for(int i=0;i<n;i++)
    {
        long long a, b;
        scanf("%lld%lld", &a, &b);
        long long l=0;
        long long r=b;
        long long x;
        long long t;
        long long k;
        for(;;)
        {
            x=(l+r)/2;
            if(x*x>b)
                r=x;
            else if(x*x<b)
                l=x;
            else
            {
                t=x;
                break;
            }
            if(r-l==1)
            {
                if(r*r>b)
                    t=l;
                else
                    t=r;
                break;
            }
        }
        l=0;
        r=a;
        for(;;)
        {
            x=(l+r)/2;
            if(x*x>a)
                r=x;
            else if(x*x<a)
                l=x;
            else
            {
                k=x;
                break;
            }
            if(r-l==1)
            {
                if(r*r>a)
                    k=l;
                else
                    k=r;
                break;
            }
        }
        if(a!=1)
        {
            t-=k;
            if((int)sqrt(a)*(int)sqrt(a)==a)
                t++;
        }
        printf("%lld\n", t);
    }
}

[USACO 2009 Dec S]Music Notes

这道题的话,好像没什么好说的,就是求了前缀和了之后,对前缀和进行二分,不够刚开始写二分,写不太明白,代码写的很烂,代码如下:

#include<stdio.h>
int main()
{
    int n, q;
    scanf("%d%d", &n, &q);
    int a[n+1];
    a[0]=0;
    for(int i=1;i<=n;i++)
    {
        int x;
        scanf("%d", &x);
        if(i==0)
            a[i]=x;
        else
            a[i]=a[i-1]+x;
    }
    //for(int i=0;i<=n;i++)
    //{
        //printf("%d ", a[i]);
    //}
    //printf("\n");
    int c;
    for(int i=0;i<q;i++)
    {
        int k;
        scanf("%d", &k);
        int l=0;
        int r=n;
        int lr;
        while(l<=r)
        {
            lr=(l+r)/2;
            if(a[lr]>k)
            {
                r=lr;
            }
            else if(a[lr]<k)
            {
                l=lr;
            }
            else
            {
                c=lr;
                break;
            }
            if(r-l==1)
            {
                c=l;
                break;
            }
        }
        printf("%d\n", c+1);
    }
    return 0;
}

[NOIP2012]借教室

这道题就是先去假设最后第几个申请不可以,然后对答案进行二分,感觉好像二分的思路基本都是这样,只不过在对答案二分时的判断条件有所不同

#include<stdio.h>
const int N=1000010;
int n, m;
int a[N];
struct nuu
{
    int dj;
    int sj;
    int tj;
};
nuu b[N];
bool f(int xx)
{
    long long cf[n+1];
    for(int i=0;i<n;i++)
    {
        if(i==0)
            cf[i]=a[i]-0;
        else
            cf[i]=a[i]-a[i-1];
    }
    for(int i=0;i<=xx;i++)
    {
        cf[b[i].sj-1]-=b[i].dj;
        cf[b[i].tj+1-1]+=b[i].dj;
    }
    long long sum=0;
    for(int i=0;i<n;i++)
    {
        sum+=cf[i];
        if(sum<0)
            return 0;
    }
    return 1;
}
int main()
{
    scanf("%d%d", &n, &m);
    for(int i=0;i<n;i++)
    {
        scanf("%d", &a[i]);
    }
    for(int i=0;i<m;i++)
    {
        scanf("%d%d%d", &b[i].dj, &b[i].sj, &b[i].tj);
    }
    int l=0, r=m;
    int xx;
    while(l<=r)
    {
        xx=(l+r)/2;
        if(f(xx))
            l=xx+1;
        else
            r=xx-1;
    }
    if(l>m)
        printf("0\n");
    else
    {
        printf("-1\n");
        printf("%d\n", l+1);
    }
    //printf("l-1:%d   l:%d   l+1:%d\n", l-1, l, l+1);
}

K-th Number

题目大意:就是给你一个n个元素的数组,把数组中的每个可以取第k大数的区区间中的第k大的数取出来构成一个新的数组,然后求新的数组里第m大的数尽可能大能是多少

这道题就对答案进行二分,即假设最后第m大的数是多少,这样的话至少就会有m给区间满足这样的条件:这些区间中都至少存在k个数是大于m的(在实现这样步的时候需要用到尺取或者主席树,但主席树我还不会,这里用的尺取)

代码如下:

#include<stdio.h>
#include<algorithm>
using namespace std;
const int N=1e5+10;
long long n, k, m;
long long a[N];
bool f(long long xx)
{
	long long sum=0;
	long long ans=0;
	for(long long lx=0, rx=0;lx<=rx&&rx<n;rx++)
	{
		if(a[rx]>=xx)
			ans++;
		while(ans>=k)
		{
			sum+=n-rx;
			if(a[lx]>=xx)
				ans--;
			lx++;
		}
	}
	if(sum>=m)
		return 1;
	else
		return 0;
}
int main()
{
	long long t;
	scanf("%lld", &t);
	for(long long i=0;i<t;i++)
	{
		scanf("%lld%lld%lld", &n, &k, &m);
		long long maxx=-1;
		for(long long j=0;j<n;j++)
		{
			scanf("%lld", &a[j]);
			maxx=max(maxx, a[j]);
		}
		long long l=0, r=maxx;
		long long xx;
		while(l<=r)
		{
			xx=(l+r)/2;
			if(f(xx))
				l=xx+1;
			else
				r=xx-1;
		}
		printf("%lld\n", l-1);
	}
}

位数差

题目:给一个数组{a},定义 h(a,b)为在十进制下 a + b 与 a 的位数差,求 

,0的位数为1。

一开始我的想法就是直接暴力,然后在找位数差的时候用二分的函数去优化,但是超时了,然后我想着去二分。但二分什么我没想懂,后面看了别人的思路才知道,是要利用分治的思想去处理,即每个区间的总的位数差=左边区间的总的位数差+右边区间的位数差+左边区间的每个数相对右边区间来说的位数差(这里用二分函数去找,速度快)

代码如下:

#include<stdio.h>
#include<math.h>
#include<algorithm>
using namespace std;
const int N = 100010;
int n;
int a[N];
int wei[9]={10,100,1000,10000,100000,1000000,10000000,100000000,1000000000};
long long f(int l, int r)
{
    if(r-l==1)
        return 0;
    int xx=(l+r)/2;
    long long sum=f(l, xx)+f(xx, r);
    sort(a+xx, a+r);
    for(int i=l;i<xx;i++)
    {
        for(int j=0;j<9;j++)
        {
            if(wei[j]-a[i]>0)
                sum+=r-(lower_bound(a+xx, a+r, wei[j]-a[i])-a);
        }
    }
    return sum;
}
int main()
{
    scanf("%d", &n);
    for(int i=0;i<n;i++)
    {
        scanf("%d", &a[i]);
    }
    int l=0, r=n;
    long long sum=f(l, r);
    printf("%lld", sum);
}

小咪买东西

大致题意:就是让你去n个手办中买k个,每个手办都有它对应的价值和花费,要求就是要让总的价值除以总的花费最大。

一开始我的思路就是去选每个价值除以花费最大的嘛,但其实这样做可能出现在求总的价值除以总的花费的时候选出来的结果不是最优的。

之后用二分思路做是这样的,对结果进行二分,然后所假设的结果的值进行验证,如果可以就再放大假设的值,不可以就缩小

代码如下:

#include<stdio.h>
#include<algorithm>
using namespace std;
const int N  = 1e4+3;
struct nuu
{
	int ci;
	int vi;
    long long vxc;
};
nuu a[N];
int n, k;
long long f(long long xx)
{
	long long sum=0;
	for(int i=0;i<n;i++)
	{
		a[i].vxc=a[i].vi-a[i].ci*xx;
	}
    sort(a, a+n, [](nuu ai, nuu bi){return ai.vxc>bi.vxc;});
    long long sumv=0, sumc=0;
    for(int i=0;i<k;i++)
    {
        sum+=a[i].vxc;
        sumv+=a[i].vi;
        sumc+=a[i].ci;
    }
	if(sum>=0)
        return sumv/sumc;
    else
        return -1;
}
int main()
{
	int t;
	scanf("%d", &t);
	while(t--)
	{
		scanf("%d%d", &n, &k);
		long long sum=0;
		for(int i=0;i<n;i++)
		{
			scanf("%d%d", &a[i].ci, &a[i].vi);
			sum+=a[i].vi;
		}
		long long l=0, r=sum;
        long long ans;
		while(l<=r)
		{
			long long xx=(l+r)/2;
            long long jilu=f(xx);
			if(jilu>=0)
            {
                l=xx+1;
                ans=jilu;
            }
			else
				r=xx-1;
		}
		printf("%lld\n", l-1);
	}
	return 0;
}

gpa

这道题其实和上一题一样是01分规划

其题意是这样的:一个学生有n 门学科,每门学科都有对应的学分s和绩点c,要求可以去掉k门学科,要让去掉后的剩余的s*c的总值除以s的总值最大,要求结果精确到1e-6

这道题也是对结果进行假设,然后二分结果,只不过呢,因为这个结果是浮点数,所以while里的判定条件需要改变从l<=r变为r-l>1e-6,l和r的每次改变也不能是l=xx+1和r==xx-1了,直接就是l=xx和r=xx

代码如下:

#include<bits/stdc++.h>
using namespace std;
int n, k;
double s[1000010];
double c[1000010];
double s_c[1000010];
bool f(double xx)
{
	for(int i=0;i<n;i++)
	{
		s_c[i]=s[i]*c[i]-s[i]*xx;
	}
	sort(s_c, s_c+n, [](double ai, double bi){return ai>bi;});;
	double ans=0;
	for(int i=0;i<n-k;i++)
	{
		ans+=s_c[i];
	}
	if(ans>=0)
		return 1;
	else
		return 0;
}
int main()
{
	scanf("%d%d", &n, &k);
	for(int i=0;i<n;i++)
	{
		scanf("%lf", &s[i]);
	}
	for(int i=0;i<n;i++)
	{
		scanf("%lf", &c[i]);
	}
	double l=0, r=1000.0;
	while(r-l>1e-5)
	{
		double xx=(l+r)/2.0;
		if(f(xx))
			l=xx;
		else
			r=xx;
	}
	printf("%.11lf\n", l);
}

其它题

这几道的思路和借教室那题的思路都是差不多的,就直接附上代码了:

[USACO 2010 Feb S]Chocolate Eating

#include<stdio.h>
int n, d;
int a[50010];
int b[50010];
long long sum;
bool f(long long xx)
{
    int ai=0;
    long long xfg=0;
    for(int i=0;i<d;i++)
    {
        for(;xfg<xx;)
        {
            if(ai>=n)
                return 0;
            xfg+=a[ai];
            ai++;
        }
        xfg=xfg/2;
    }
    return 1;
}
void af(long long xx)
{
    int ai=0;
    long long xfg=0;
    for(int i=0;i<d;i++)
    {
        //printf("%lld ", xfg);
        for(;xfg<xx;)
        {
            if(ai>=n)
                goto end;
            xfg+=a[ai];
            b[ai]=i+1;
            ai++;
        }
        xfg=xfg/2;
    }
    end:
    //printf("\nnx%d\n", nx);
    for(int i=0;i<n;i++)
    {
        if(b[i]!=0)
            printf("%d\n", b[i]);
        else
            printf("%d\n", d);//巧克力没吃完,所以剩下的巧克力都会在最后一天被吃
    }
}
int main()
{
    scanf("%d%d", &n, &d);
    for(int i=0;i<n;i++)
    {
        scanf("%d", &a[i]);
        sum+=a[i];
    }
    long long l=0, r=sum;
    long long xx;
    while(l<=r)
    {
        xx=(l+r)/2;
        if(f(xx))
            l=xx+1;
        else
            r=xx-1;
    }
    printf("%lld\n", l-1);
    af(l-1);
}

华华给月月准备礼物

#include<stdio.h>
#include<algorithm>
using namespace std;
int main()
{
    long long n, k;
    scanf("%lld%lld", &n, &k);
    long long a[n];
    for(int i=0;i<n;i++)
    {
        scanf("%lld", &a[i]);
    }
    sort(a, a+n);
    long long l=1, r=a[n-1];
    while(l<=r)
    {
        long long xx=(l+r)/2;
        long long res=0;
        for(int i=0;i<n;i++)
        {
            res+=a[i]/xx;
        }
        if(res>=k)
            l=xx+1;
        else if(res<k)
            r=xx-1;
        else
        {
            l=xx+1;
            break;
        }
    }
    printf("%lld", l-1);
    return 0;
}

[CQOI2010]扑克牌

#include<stdio.h>
const int N=1000010;
int n, m;
int a[N];
struct nuu
{
    int dj;
    int sj;
    int tj;
};
nuu b[N];
bool f(int xx)
{
    long long cf[n+1];
    for(int i=0;i<n;i++)
    {
        if(i==0)
            cf[i]=a[i]-0;
        else
            cf[i]=a[i]-a[i-1];
    }
    for(int i=0;i<=xx;i++)
    {
        cf[b[i].sj-1]-=b[i].dj;
        cf[b[i].tj+1-1]+=b[i].dj;
    }
    long long sum=0;
    for(int i=0;i<n;i++)
    {
        sum+=cf[i];
        if(sum<0)
            return 0;
    }
    return 1;
}
int main()
{
    scanf("%d%d", &n, &m);
    for(int i=0;i<n;i++)
    {
        scanf("%d", &a[i]);
    }
    for(int i=0;i<m;i++)
    {
        scanf("%d%d%d", &b[i].dj, &b[i].sj, &b[i].tj);
    }
    int l=0, r=m;
    int xx;
    while(l<=r)
    {
        xx=(l+r)/2;
        if(f(xx))
            l=xx+1;
        else
            r=xx-1;
    }
    if(l>m)
        printf("0\n");
    else
    {
        printf("-1\n");
        printf("%d\n", l+1);
    }
    //printf("l-1:%d   l:%d   l+1:%d\n", l-1, l, l+1);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值