您的位置:首页 > 其它

二分答案 & 三分法

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

        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

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: