二分答案 & 三分法

        二分法是竞赛中常用的一种降低时间复杂度的方法,其中二分答案是竞赛中常用的算法。它的使用范围较为明确,无非就是两种情况。

        1.求方程……=……的 (仅能求出少量的根,较广泛适用于完全单调递增或递减的方程中)

        2.求某种情况的最大最小值最小最大值

 

        二分答案的模板也非常简单,基本上所有的二分答案主程序代码如下:

 

        while (right-left > eps)
        {
            int mid = (left+right)/2;
            if (solve(mid)) right = mid;
            else left = mid;
        }


         那么,既然二分答案思想简单,为何要进行总结呢?首先我们应该明确,二分答案的难点并不在于它的思想,而在于那个solve()函数

        下面看几道例题:

UVa 10341 Solve it!

        解方程 p*e^(-x)+q*sin(x)+r*cos(x)+s*tan(x)+t*x^2+u=0,其中x∈[0,1]。输入p,q,r,s,t;

        很简单的一道二分求根法,f(x)=p*e^(-x)+q*sin(x)+r*cos(x)+s*tan(x)+t*x^2+u在x∈[0,1]时严格递增,可用勘根定理来求解,solve函数即为f(x) 的值,核心代码如下:

 

double solve(double x)
{
	return p*exp(-x)+q*sin(x)+r*cos(x)+s*tan(x)+t*x*x+u;
}

if (solve(0)*solve(1) > 0) printf("No solution!\n");
else
{	
	while (right-left > eps)
	{
		double mid = (right+left)/2;
		if (solve(left)*solve(mid) < 0) right = mid;
		else left = mid;
	}
}

 

P.S.:基本上所有的判根都可以使用以上的代码,唯一不同的就是它的solve函数还有初始left值与right值。

 

UVa 11090 Going in Cycle!!

        给定一个n个点m条边的加权有向图,求平均权值最小的回路。

        平均权值的出现似乎令我们吓了一跳,因为平时只接触过最短或最长。但由于题目中给出的是回路,又是求路径,不难想到和负权回路有关系。于是我们猜测存在一个平均权值小于mid的回路,这条回路上的各条边的权值分别为w1,w2,w3......wk,则w1+w2+......wk<k*mid,即(w1-mid)+(w2-mid)+......(wk-mid)<0,负权回路!

       于是我们可以将所有的边减去mid,在判断有没有负权回路即可。

核心代码如下:

#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;

const int MAXN=1001;
const int INF=100000;
const double eps = 1e-4;

struct Node
{
    int to;
    double dist;
};

int n,m;
double dist[MAXN];//离源点的最短距离 
int cnt[MAXN]; //判断是否存在负权回路 
bool inq[MAXN];//是否在队列中 
vector <Node> G[MAXN];

bool SPFA(int s)
{
    for (int i=1;i<=n;i++) dist[i]=INF;
    memset(inq,false,sizeof(inq));
    dist[s] = 0;inq[s] = true;
    queue <int> Q;
    Q.push(s);
    while (!Q.empty())
    {
        int x=Q.front();Q.pop();
        inq[x]=false;
        for (int i=0;i<G[x].size();i++)
            if (dist[x]+G[x][i].dist<dist[G[x][i].to])
            {
                dist[G[x][i].to]=dist[x]+G[x][i].dist;
                if (!inq[G[x][i].to])
                {
                    Q.push(G[x][i].to);
                    inq[G[x][i].to]=true;
                    if (++cnt[G[x][i].to] > n) return true;
                }
            }
    } 
    return false;
}

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++)
    {
        int from,to,dist;
        scanf("%d%d%d",&from,&to,&dist);
        G[from].push_back((Node){to,dist});        
    }
    double left = 0, right = 10000;
    bool a= SPFA(1);
    while (right-left > eps)
    {
        double mid = (right+left)/2;
        for (int i=1;i<=n;i++)
            for (int j=0;j<G[i].size();j++) G[i][j].dist -= mid;
        bool Negative_Ctyle = SPFA(1);
        for (int i=1;i<=n;i++)
            for (int j=0;j<G[i].size();j++) G[i][j].dist += mid;
        if (Negative_Ctyle) right = mid;
        else left = mid;
    }
    printf("%lld\n",left);
    return 0;
} 


LA 4254

        本题也是一题经典的最大最小值,它的解法很简单,就是二分枚举速度,看以目前速度处理器是否可以解决。于是所有的难点就是这个solve()函数。其实我们可以以贪心的做法,将结束时间早的尽快处理,代码中使用了优先队列来维护操作,也可以使用线段树进行维护,代码如下:

#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <algorithm>

using namespace std;

const int MAXN = 10001;
const int INF = 10000001;

struct Node
{
    double r,d,w;
    bool operator < (const Node &x) const
    {
        return d > x.d;
    }
} data[MAXN];

int n,t;

bool cmp(Node x,Node y)
{
    return x.r < y.r;
}

bool solve(int speed)
{
    priority_queue <Node> Heap;
    double now_time = data[1].r;
    for (int i=1;i<=n;i++)
    {
        now_time = max(now_time,data[i].r);
        Heap.push(data[i]);
        while (now_time < data[i+1].r && !Heap.empty())
        {
            Node x = Heap.top();Heap.pop();
            if (x.d < now_time+(x.w/speed)) return false;
            if ((double)x.w/speed <= (data[i+1].r-now_time)) now_time += (double)x.w/speed;
            else 
            {
                x.w -= speed*(data[i+1].r-now_time);
                Heap.push(x);
                now_time = data[i+1].r;
            }
        }
    }
    return true;
}

int main()
{
    scanf("%d",&t);
    while (t--)
    {
        scanf("%d",&n);
        memset(data,0,sizeof(data));
        for (int i=1;i<=n;i++) scanf("%lf%lf%lf",&data[i].r,&data[i].d,&data[i].w);
        int min_speed = 0, max_speed = 1000001;
        data[n+1].r = INF;
        sort(data+1,data+n+1,cmp);
        while (min_speed+1 != max_speed)
        {
            int mid = (min_speed+max_speed)/2;
            if (solve(mid)) max_speed = mid;
            else min_speed = mid;
        }
        printf("%d\n",max_speed);
    }
    return 0;
}

 

        三分法是后来加上去的,因为其性质与二分法差不多,我们姑且把它们归为同类的算法,三分法有什么用呢?求一个单峰函数的最值。

        什么叫做单峰函数,根据高中数学4-7定义,函数f(x)在[a,b]上有唯一最值点C,且在C的左边与右边都严格单调递增或递减,那么称这个函数在[a,b]间为单峰函数。

        三分法求最值过程很简单,这里就不细讲了,仅仅贴出代码:

while (r-l > eps)
{
    double mid1 = l+(r-l)/3;
    double mid2 = r-(r-l)/3;
    if (f(mid1) < f(mid2)) r = mid2; else l = mid1; 
}


 

 

后记:
        二分的作用其实不止这些,实际上,倍增算法(将时间复杂度为O(N)降为O(log N))运用二分的思想在各种算法中广泛应用。下面给出几道二分答案的题目。

        UVa 10382


 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值