分治法

本文深入探讨了分治法的概念、适用条件,并通过三个具体的编程实例——幂次方计算、一元三次方程求解和平面上的最接近点对问题,详细展示了分治法的运用。其中,对于平面上的最接近点对问题,提供了两种不同的解决方案,一种是经典的分治加归并策略,另一种是随机旋转和排序的启发式方法。
摘要由CSDN通过智能技术生成

转载时请标明来源

概述

本文介绍了分治法,并用几道例题来具体阐述。

什么是分治法?

百度百科:分治法

分治,字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。

分治法是很多高效算法的基础,如排序算法(快速排序,归并排序)。

分治法适用条件

采用分治法解决的问题一般具有的特征如下:

  1. 问题的规模缩小到一定的规模就可以较容易地解决。
  2. 问题可以分解为若干个规模较小的模式相同的子问题,即该问题具有最优子结构性质。
  3. 合并问题分解出的子问题的解可以得到问题的解。
  4. 问题所分解出的各个子问题之间是独立的,即子问题之间不存在公共的子问题。

例题

1. 幂次方

洛谷:P1010 [NOIP1998 普及组] 幂次方

难度:⭐

思路

简单题,没什么好说的。

代码

#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. 平面上的最接近点对

思路(一)

百度文库:《分治算法之平面最接近点问题》

对横坐标排序,然后分治、归并。重点在于归并步骤。

注意: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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值