二分答案 & 三分法
2014-06-29 12:31
204 查看
二分法是竞赛中常用的一种降低时间复杂度的方法,其中二分答案是竞赛中常用的算法。它的使用范围较为明确,无非就是两种情况。
1.求方程……=……的根 (仅能求出少量的根,较广泛适用于完全单调递增或递减的方程中)
2.求某种情况的最大最小值或最小最大值
二分答案的模板也非常简单,基本上所有的二分答案主程序代码如下:
那么,既然二分答案思想简单,为何要进行总结呢?首先我们应该明确,二分答案的难点并不在于它的思想,而在于那个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) 的值,核心代码如下:
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()函数。其实我们可以以贪心的做法,将结束时间早的尽快处理,代码中使用了优先队列来维护操作,也可以使用线段树进行维护,代码如下:
三分法是后来加上去的,因为其性质与二分法差不多,我们姑且把它们归为同类的算法,三分法有什么用呢?求一个单峰函数的最值。
什么叫做单峰函数,根据高中数学4-7定义,函数f(x)在[a,b]上有唯一最值点C,且在C的左边与右边都严格单调递增或递减,那么称这个函数在[a,b]间为单峰函数。
三分法求最值过程很简单,这里就不细讲了,仅仅贴出代码:
后记:
二分的作用其实不止这些,实际上,倍增算法(将时间复杂度为O(N)降为O(log N))运用二分的思想在各种算法中广泛应用。下面给出几道二分答案的题目。
UVa 10382
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
相关文章推荐
- [BZOJ 2654] tree · 二分答案
- [HDU 2692] Ball · 二分答案+最短路
- HDU 2333 & POJ 3497 & UVA 12124 Assemble (二分答案)
- [BZOJ4556][TJOI2016&&HEOI2016]字符串(二分答案+后缀数组+RMQ+主席树)
- [NOIP2015提高&洛谷P2678]跳石头 题解(二分答案)
- UVA1607 与非门电路&&二分答案
- 【刷水-二分答案】BZOJ1650 & BZOJ1639
- 【题】【二分答案&倍增(lca)】NKOJ 3560 运输计划
- 【二分答案 && 贪心】codeforces-847E Packmen
- hihoCoder 1139 二分·二分答案
- [二分答案+对偶图 || 三角剖分] BZOJ 3007 拯救小云公主 && BZOJ 4219 跑得比谁都快
- [二分答案 上下界费用流验证] 计蒜客 91 地铁 & HDU 5263 平衡大师
- [NOIP提高&洛谷P1024]一元三次方程求解 题解(二分答案)
- bzoj 3993: [SDOI2015]星际战争 二分答案&网络流
- 最小生成树 || ( BFS && 二分答案) —— 营救
- 【hihoCoder 1139】 二分·二分答案
- 二分答案法、三分法
- [BZOJ4552][TJOI2016&&HEOI2016]排序(二分答案+线段树)
- CH Round #72树洞[二分答案 DFS&&BFS]
- 再解“书的复制” 二分答案+贪心