转载时请标明来源
概述
本文介绍了分治法,并用几道例题来具体阐述。
什么是分治法?
分治,字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
分治法是很多高效算法的基础,如排序算法(快速排序,归并排序)。
分治法适用条件
采用分治法解决的问题一般具有的特征如下:
- 问题的规模缩小到一定的规模就可以较容易地解决。
- 问题可以分解为若干个规模较小的模式相同的子问题,即该问题具有最优子结构性质。
- 合并问题分解出的子问题的解可以得到问题的解。
- 问题所分解出的各个子问题之间是独立的,即子问题之间不存在公共的子问题。
例题
1. 幂次方
难度:⭐
思路
简单题,没什么好说的。
代码
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<math.h>
#define condition(_c,_t,_f) ((_c)?(_t):(_f))
#define abs(x) condition((x)>=0,(x),-(x))
#define min(x,y) condition((x)<(y),x,y)
#define max(x,y) condition((x)>(y),x,y)
using namespace std;
string solve(int num){
switch(num){
case 0: return "0";
case 1: return "2(0)";
case 2: return "2";
default:
string str="";
while(num>0){
int pow,mul;
for(pow=0,mul=1;mul<=num;mul*=2,pow++);
if(pow-1==1) str+="2";
else str+="2("+solve(pow-1)+")";
num-=mul/2;
if(num>0)str+="+";
}
return str;
}
}
int main(){
int n;
cin>>n;
cout<<solve(n);
return 0;
}
2. 一元三次方程求解
洛谷:P1024 [NOIP2001 提高组] 一元三次方程求解
难度:⭐
思路
二分法判断解的位置,注意:为了让端点不被计入两次,可以判断半闭半开区间。
代码
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<math.h>
double a,b,c,d;
#define f(x) (a*(x)*(x)*(x)+b*(x)*(x)+c*(x)+d)
using namespace std;
void solve(double left, double right){
//printf("SOLVE(%lf,%lf) f1=%lf, f2=%lf\n",left,right,f(left),f(right));
double f1=f(left),f2=f(right);
if(f1*f2>0 && right-left<1) return;
if(f1*f2<=0
&& f2!=0/*Avoid computing twice*/
&& right-left<0.004f) printf("%.2lf ",left);
else{
solve(left, left+(right-left)/2);
solve(left+(right-left)/2,right);
}
}
int main(){
scanf("%lf %lf %lf %lf",&a,&b,&c,&d);
solve(-100,100.5);
}
3. 平面上的最接近点对
-
洛谷:P1257 平面上的最接近点对
难度:⭐ -
洛谷:P1429 平面最近点对(加强版)
难度:⭐⭐⭐
思路(一)
对横坐标排序,然后分治、归并。重点在于归并步骤。
注意:qsort是快排,时间复杂度最坏情况为 O ( n ) O(n) O(n),据说(我没看源码,不清楚)C++标准库里的sort时间复杂度为 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n),如果点列顺次排成一条线,此时是qsort排序的最坏情况。为了让程序在最坏情况下也可以完美运行,建议用时间复杂度稳定为 O ( n l o g n ) O(nlogn) O(nlogn)的做法,例如二分归并排序。(在本代码中直接使用的sort)
代码(一)
#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
#define condition(_c,_t,_f) ((_c)?(_t):(_f))
#define abs(x) condition((x)>=0,(x),-(x))
#define min(x,y) condition((x)<(y),x,y)
#define max(x,y) condition((x)>(y),x,y)
using namespace std;
typedef struct{
double x,y;
}point;
#define MAXN 200002
point pt[MAXN];
int tmp[MAXN];//tmp数组存的是pt的索引
int n;
bool cmpr(point p1, point p2){
return p1.x<p2.x || (p1.x==p2.x&&p1.y<p2.y);
}
bool cmprtmp(int i1, int i2){
return pt[i1].y<pt[i2].y;
}
double dis(point p1, point p2){
return sqrt((p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y));
}
// 分治算法
double dvs(int l, int r){
if(l==r) return 99999999;
if(l+1==r) return dis(pt[l],pt[r]);
// 分治步骤
int mid=l+(r-l)/2;
double d1=dvs(l,mid);
double d2=dvs(mid,r);
// 归并步骤
double d=min(d1,d2);
int count=0;//与mid的Δx < d,该宽度为2d的带状区域上点数
for(int i=mid+1; pt[i].x-pt[mid].x<d && i<=r; i++) tmp[count++]=i;
for(int i=mid-1; pt[mid].x-pt[i].x<d && i>=l; i--) tmp[count++]=i;
sort(tmp,tmp+count,cmprtmp);
for(int i=0;i<count;i++)
for(int j=i+1;j<count && pt[tmp[j]].y-pt[tmp[i]].y<d;j++)
d=min(d,dis(pt[tmp[j]],pt[tmp[i]]));
return d;
}
int main(){
scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%lf %lf",&pt[i].x,&pt[i].y);
sort(pt,pt+n,cmpr);
printf("%.4lf",dvs(0,n-1));
return 0;
}
思路(二)
思路来源:洛谷 @yuy_
注:此思路并非绝对正确,仅仅有很大概率通过测试机。(小心被Hack!!)
距离最短的两个点,在整个平面旋转时,他们横坐标之差最小的概率最大。利用这一点,随机绕原点旋转几次,对横坐标排序,选取每个点后面若干个点计算距离,取这些距离的最小值,大概率就能通过测试机。
代码(二)
转载自洛谷 @yuy 的博客
#include<bits/stdc++.h>
#define pi acos(-1.0)
using namespace std;
struct node{
double x,y;
}a[200005];
int n;
double ans=1e15;//初始化答案
bool cmp(node a,node b){
return a.x<b.x;//按照横坐标排序
}
double dis(node a,node b){
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));//计算两点之间距离
}
void calc(){
for (int i=1;i<=n;i++)
for (int j=i+1;j<=i+5&&j<=n;j++)
ans=min(ans,dis(a[i],a[j]));
}
void around(double ds){
ds=ds/180.0*pi;//角度转换为弧度
for (int i=1;i<=n;i++){
double x=a[i].x,y=a[i].y;//旋转前的点
double xn,yn;//旋转后的点
double xyu=0.0,yyu=0.0; //旋转中心
xn=(x-xyu)*cos(ds)-(y-yyu)*sin(ds)+xyu;
yn=(x-xyu)*sin(ds)+(y-yyu)*cos(ds)+yyu;
a[i].x=xn,a[i].y=yn;
}
sort(a+1,a+1+n,cmp);//排序
calc();//计算
}
int main(){
srand(time(NULL));
scanf("%d",&n);
for (int i=1;i<=n;i++){
scanf("%lf%lf",&a[i].x,&a[i].y);
}
around(0);//将原图像进行排序并枚举每个点与其后五个点比较
around(rand()%360);//将图像随机(0°~359°)旋转 并排序 计算
around(rand()%360);//将图像随机(0°~359°)旋转 并排序 计算
printf("%.4lf\n",ans);
return 0;
}