2023年贵州信息技术教师培训(day19 分治)

分治算法

什么是分治算法(Divide and Conquer)是一种解决复杂问题的算法设计技巧,它的基本思想是将一个大问题分解为若干个较小的子问题,然后递归地解决这些子问题,最后将子问题的解合并为原问题的解。
【分治的优化】
分治算法的优点是可以简化复杂问题的求解过程,利用递归和分而治之的思想,降低问题的难度和复杂度。分治算法也可以利用并行计算和缓存优化等技术提高效率。
【可以解决的问题】
该问题的规模缩小到一定的程度就可以容易地解决;
该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质;
利用该问题分解出的子问题的解可以合并为该问题的解;
该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题。
【算法步骤】
分治算法可以用递归来实现,分治算法的一般步骤是:

  1. 分解:将原问题分解为若干个规模较小,相互独立 ,与原问题形式相同的子问题;
  2. 解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题;
  3. 合并:将各个子问题的解合并为原问题的解。

归并排序

归并排序是一种基于分治思想的排序算法,它的基本思路是:

  1. 分解:将待排序的数组分成两个部分,左半部分和右半部分,如果数组长度大于1,则继续分解;
  2. 解决:对左半部分和右半部分分别进行归并排序,递归地调用自身;
  3. 合并:将排好序的左半部分和右半部分合并成一个有序的数组。

例 三友行OJ( www.oj7.c)求逆序对。

【题目描述】有一实数序列A[1]、A[2]、A[3]、……A[n-1]、A[n](n<10000),若i<j,并且A[i]>A[j],则称A[i]、A[j]构成了一个逆序对,求数列A中逆序对的个数。
例如,5 2 4 6 2 3 2 6,可以逆序对有
(5 2),(5 4),(5 2),(5 3) ,(5 2),
(4 2)(4 3)(4 2 ),
(6 2),(6 3),(6 2),
(3 2)
共:12个
【输入】
共两行,第一行有一个正整数n,第二行有n个整数。
【输出】
只有一行为逆序对个数。
【样例输入】

8
5 2 4 6 2 3 2 6

【样例输出】

12
#include<bits/stdc++.h>
using namespace std;
#define MAXN 10001
int n,a[MAXN],r[MAXN];
long long ans=0;
void MergSort(int s,int t){//s是起点,t是终点
	if(s==t) return;
	int m=(s+t)/2;//也可以使用int m=(s+t)>>1;
	MergSort(s,m);
	MergSort(m+1,t);
	//到这一步就是归并排序完成,左右都有序
	int i=s;
	int j=m+1;
	int k=s;
	while(i<=m && j<=t){
		if(a[i]<=a[j])
			r[k++]=a[i++];
		else{
			ans+=m-i+1;
			r[k++]=a[j++];
		} 			
	}
	while (i<=m) r[k++]=a[i++];
	while (j<=t) r[k++]=a[j++];
	for(i=s;i<=t;i++) a[i]=r[i];
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	MergSort(1,n);
	cout<<ans;
	return 0;
}
归并排序算法是一个典型的分治算法,它将待排序的数组分成两个部分,对每个部分进行归并排序,然后将两个有序的部分合并成一个有序的数组。归并排序算法的递推式为

一元三次方程求解

有形如 a x 3 + b x 2 + c x + d = 0 ax^3+bx^2+cx+d=0 ax3+bx2+cx+d=0这样的一个一元三次方程。给出该方程中各项的系数(a,b,c,d均为实数),并约定该方程存在三个不同实根(根的范围在-100至100之间),且根与根之差的绝对值>=1。
要求由小到大依次在同一行输出这三个实根(根与根之间留有空格),并精确到小数点后2位。
提示:记方程f(x)=0,若存在2个数x1和x2,且x1<x2,,f(x1)*f(x2)<0,则在(x1,x2)之间一定有一个根。

【输入】
a,b,c,d
【输出】
三个实根(根与根之间留有空格)
【输入样例】
【输入】
1 -5 -4 20
【输出】
-2.00 2.00 5.00

【思路分析】
在这里插入图片描述
思路1 :枚举法
根据根的值域和根与根之间的间距要求(>=1),我们不妨将根的值域扩大100倍(-10000<=x<=10000),依次枚举该区间的每一个整数值x,并在题目要求的精度内设定区间x1=,x2=。若区间端点的函数值f(x1)和f(x2)异号或者在区间端点x1的函数值f(x1)=0,则确定为f(x)=0的一个根。
由此得出算法:
输入方程中各项的系数a,b,c,d;

for(x=-10000; x  <= 10000; x++){//枚举当前根*100的可能范围
	x1=(x-0.05)/100;x2=(x+0.05)/100;//在题目要求的精度内设定区间
	if(f(x1)*f(x2) < 0 || f(x1)==0) //若在区间两端的函数值异号或在x1处的函数值为0,则确定x/100为根
	printf("%.2f",x/100);
}

//其中函数f(x)计算$a*x^3+b*x^2+c*x+d$:
double f(double x){
	f=a*x*x*x+b*x*x+c*x+d;
}

思路2:分治法
枚举根的值域中的每一个整数x(100<=x<=100)。由于根与根之差的绝对值>=1,因此设定搜索区间[x1,x2],其中x1=x,x2=x+1。若
(1)f(x1)=0,则确定x1为f(x)的根;
(2)f(x1)*f(x2)>0,则确定根x不在区间[x1,x2]内,设定[x2,x2+1]为下一个搜索区间;
(3)f(x1)*f(x2)<0,则确定根x在区间[x1,x2]内。
如果确定根x在区间[x1,x2]内的话 (f(x1)*f(x2)<0),如何在该区间找到根的确切位置。采用二分法,将区间[x1,x2]分成左右两个子区间:左子区间[x1,x]和右子区间[x,x2](其中x=):
如果f(x1)*f(x2)<=0,则确定根在左区间[x1,x]内,将x设定为该区间的右指针(x2=x),继续对左区间进行对分;如果f(x1)*f(x)>0,则确定根在右区间[x,x2]内,将x设为该区间的左指针(x1=x),继续对右区间进行对分;
上述对分过程一直进行到区间的间距满足精度要求为止(x2-x1<0.001)。此时确定x1为f(x)的根。

#include<bits/stdc++.h>
using namespace std;
float a,b,c,d;
int n;
float ans[4];
float f(float x){
	return a*x*x*x+b*x*x+c*x+d;
}
void solve(float l,float r){
	if(f(l)*f(r)>0 && ((r-1)<1 || n>=2)) return ;
	float mid=(l+r)/2;
	if(f(mid)<=1e-4 && f(mid)>=1e4 ){//近似为0
		ans[++n]=mid;
		return;
	}
	solve(l,mid);
	solve(mid,r);
}

int main()
{
	cin>>a>>b>>c>>d;
	solve(-100,100);
	cout<<fixed<<setprecision(2)<<ans[1]<<" "<<ans[2]<<" "<<ans[3]<<endl;
	return 0;
}

循环比赛日程表(match)

【问题描述】
设有N个选手进行循环比赛,其中N= 2 M 2^M 2M,要求每名选手要与其他N-1名选手都赛一次,每名选手每天比赛一次,循环赛共进行N-1天,要求每天没有选手轮空。
输入:M
输出:表格形式的比赛安排表
【样例输入】match.in

3

【样例输出】match.out

1 2 3 4 5 6 7 8 
2 1 4 3 6 5 8 7
3 4 1 2 7 8 5 6
4 3 2 1 8 7 6 5
5 6 7 8 1 2 3 4 
6 5 8 7 2 1 4 3 
7 8 5 6 3 4 1 2
8 7 6 5 4 3 2 1

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
const int Maxn=33,Maxm=5;
int matchlist[Maxn][Maxn];
int m;
int main()
{
	cin>>m;
	int n=1<<m;//2的m次方
  即$2^m$
  	int k=1,half=1;
  	matchlist[0][0]=1;
  	while(k<=m){
  		for(int i=0;i<half;i++)
  			for(int j=0;j<half;j++)
  			{
  				matchlist[i][j+half]=matchlist[i][j]+half;
  			}
  			for(int i=0;i<half;i++)
  			for(int j=0;j<half;j++)
  			{
  				matchlist[i+half][j]=matchlist[i][j+half];
  				matchlist[i+half][j+half]=matchlist[i][j];
  			}
  			half*=2;
  			k++;
  	}
  	for(int i=0;i<n;i++){
  		for(int j=0;j<n;j++)
  			cout<<matchlist[i][j]<<" "<<;
  		cout<<endl;
  	}	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值