算法入门经典第八章学习笔记(中)

8.3  递归与分治

8.3.1  棋盘覆盖问题

有一个2^k×2^k个方格棋盘,恰有一个方格是灰色的,其他为白色,你的任务是用包含3个方格的L型骨牌覆盖所有白色方格。灰色方格不能被子覆盖,且任意一个白色方格不能同时被两个或更多骨牌覆盖。如图8-3所示为L型骨牌(三格板)的4种旋转方式。

方法也是分治法,总牌数(4^k-1)/3 ,划分成下面(a)这样,第一块和原问题一样,但是其他三块不行。所以这样处理选择一个三格板放在(b)位置上,如图,就可以据需处理了。

#include <iostream>
using namespace std;
const int N = 11;
int Board[N][N];
int title = 0;
//tr表示棋盘左上角方格的行号,tc表示棋盘左上角方格的列号, 
//dr表示特殊方格所在的行号,dc表示特殊方格所在的列号,size表示方形棋盘边长。 
//title表示L型骨牌的编号,其初始值为0

void ChessBoard(int tr, int tc, int dr, int dc, int size)
{
	if (1 == size)
		return ;
	int t = ++title, s = size / 2;
	if (dr < tr+s && dc < tc+s)
		ChessBoard(tr, tc, dr, dc, s);//特殊方格在此棋盘中
	else
	{//无特殊方格了
		//用t号骨型牌覆盖右下角
		Board[tr+s-1][tc+s-1] = t;
		//覆盖其余方格
		ChessBoard(tr, tc, tr+s-1, tc+s-1, s);
	}
	//覆盖右上角棋盘
	if (dr < tr+s && dc >= tc+s)
		ChessBoard(tr, tc+s, dr, dc, s); 
	else
	{       
		Board[tr+s-1][tc+s] = t;        
		ChessBoard(tr, tc+s, tr+s-1, tc+s, s);  
	} 
	//覆盖左下角子棋盘
	if(dr>=tr+s && dc<tc+s) 
		ChessBoard(tr+s, tc, dr, dc, s); 
	else  
	{
		Board[tr+s][tc+s-1] = t;   
		ChessBoard(tr+s, tc, tr+s, tc+s-1, s); 
	}
	//覆盖右下角子棋盘
	if(dr>=tr+s && dc>=tc+s)   
		ChessBoard(tr+s, tc+s, dr, dc, s);  
	else 
	{    
		Board[tr+s][tc+s] = t; 
		ChessBoard(tr+s, tc+s, tr+s, tc+s, s);  
	} 
}
void DisplayBoard(int size)
{   
	for(int i=1; i<=size; ++i)
	{        
		for(int j=1; j<=size; ++j)   
			printf("%2d ", Board[i][j]); 
		printf("\n");   
	} 
}

int main() 
{     
	ChessBoard(1, 1, 2, 1, 4);
	DisplayBoard(4); 
	return 0;
}

8.3.2  

循环日程表问题

 

8.3.2  循环日程表问题 

设有n=2k 个运动员要进行网球循环赛。需要设计比赛日程表。每个选手必须与其他n-1个选手个比赛一次;每个选手一天只能赛一次;循环赛一共进行n-1天。按此要求设计一张比赛日程表,它有n行和n-1列,第i行第j列为第i个选手在第j天遇到的选手。

方法:递归。红框是k = 1时的。
#include <iostream>  
#include <iomanip>  
#include <string>  
#include <cstring>  
#include <cstdio>  
#include <queue>  
#include <stack>  
#include <algorithm>  
#include <cmath>  

using namespace std;

int table [100][100];

void Creattable(int r1, int c1, int r2, int c2, int size)
{
	int i, j;
	int halfsize = size/2;
	if (size > 1)
		Creattable(0, 0, halfsize, halfsize, halfsize);
	else
		table[0][0] = 1;
	for (i = 0; i < size; i++)
		for (j = 0; j < size; j++)
		{
			if (i < halfsize && (j >= halfsize && j < size))//右上角的行程是左上角的行程加上halfsize	
				table[i][j] = table[i][j-halfsize] + halfsize;
			if ((i >= halfsize && i < size) && j < halfsize)//左下角的行程和右上角的一样
				table[i][j] = table[i-halfsize][j+halfsize];
			if ((i >= halfsize && i < size) && ( j >= halfsize && j < size))//右下角和坐上角一样
				table[i][j] = table[i-halfsize][j-halfsize];
		}
}

int main() 
{
#ifdef Local    
	freopen("a.in", "r", stdin);    
	freopen("a.out", "w", stdout);    
#endif
	int i, j, k, n = 1;
	cin >> k;
	for (i = 1; i <= k; i++)
		n = n*2;//计算2^k 用pow也行。 
	Creattable(0, 0, n, n, n);
	for (i = 0; i < n; i++)
	{
		cout << "运动员" << table[i][0] << "的每日赛程" ;
		for (j = 1; j < n; j++)
			cout << table[i][j] << " ";
		cout << endl;
	}
	return 0;
}

8.3.3  巨人与鬼

8.3.4  非线性方程求根

一次向银行借a元钱,分b月还清。如果需要每个月还c元,月利率是多少?(按复利计算)?例如借2000元,分4个月每月510元,则月利率为0.797%。答案应不超过100%。 
方法:因为方程为:  f(x)=(((2000(1+x)-c)(1+x)-c)(1+x)-c)(1+x)-c=0很难求解x,但是注意到f(x)在x∈[0,100]内关于是单调递增的,因此f(x)和0的关系与x和方程的解x0的大小关系等价。可以采用二分法来解决此问题。也就是去猜结果,当然首先的式子和思路要有。
#include <iostream>  
#include <iomanip>  
#include <string>  
#include <cstring>  
#include <cstdio>  
#include <queue>  
#include <stack>  
#include <algorithm>  
#include <cmath>  

using namespace std;

int main() 
{
#ifdef Local    
	freopen("a.in", "r", stdin);    
	freopen("a.out", "w", stdout);    
#endif
	double a = 0, c = 0, x = 0, y = 100;
	int i, b;
	cin >> a >> b >> c;
	while (y - x > 1e-5)
	{
		double m = x + (y-x)/2;
		double f = a;
		for (i = 0; i < b; i++)
			f += f*m/100.0 - c;//每个月需要还的,b次b个月
		if (f < 0)
			x = m;
		else
			y = m;
	}
	cout << setprecision(3) << fixed << x << "%" << endl;
}

注意到f(x)在x∈[0,100]内关于是单调递增的,因此f(x)和0的关系与x和方程的解x0的大小关系等价。可以采用二分法来解决此问题。

8.3.5  最大值最小化

把一个包含n个正整数的序列划分成m个连续的子序列(每个正整数恰好属于一个序列)。设i个序列的各数之和为S(i),你的任务是让所有S(i)的最大值尽量小。例如序列1 2 3 2 5 4划分成3个序列的最优方案为1 2 3|2 5|4,其中S(1)、S(2)、S(3)分别为6、7、 4,最大值为7;如果划分成1 2|3 2|5 4,最大值为9,不如刚才好。n≤10^6 。所有数之和 不超过10^9 。
方法:对于这个问题:能否把输入序列划分成m个连续的子序列,使得所有S(i)均不超过x?此问题的答案用P(x)表示,则让P(x)为真的最小x就是原题的答案。对P(x)计算,每次尽量往右划分即可。
先求出序列的总和和最小值,x必定在这之间,二分判断每个x即可。注意判定函数,从左往右,如果和大于mid,就表示前边的是划分出来的一段。最后看符合条件的有几段,如果大于m段,表明这个x不符合,并且是x小了。所以递归的是x到max。
代码中的随机函数是来产生a数组的,并个数N,段数m都是定义的,也可以改一改变成自己输出。
#include <iostream>  
#include <iomanip>  
#include <string>  
#include <cstring>  
#include <cstdio>  
#include <queue>  
#include <stack>  
#include <algorithm>  
#include <cmath>  
#include <ctime>

using namespace std;

#define N 10
#define INF 1000

int judge(int a[], int mid, int k)
{
	int i, seg = 0, sum = 0;
	for (i = 0; i < N; i++)
	{
		sum += a[i];
		if (sum > mid)
		{
			sum = a[i];
			seg++;
		}
	}
	if (seg >= k)
		return 0;
	else
		return 1;
}

int value(int a[], int low, int high, int segment)
{
	if (low > high)
		return high+1;
	else
	{
		int mid = (low+high)/2;
		if (judge(a, mid, segment) == 1)
			return value(a, low, mid-1, segment);
		else
			return value(a, mid+1, high, segment);
	}
}

int main() 
{
#ifdef Local    
	freopen("a.in", "r", stdin);    
	freopen("a.out", "w", stdout);    
#endif
	int srand((unsigned)time(NULL));
	int a[N];
	for (int ifor = 0; ifor < N; ifor++)//产生a数组
		a[ifor] = rand()%20;
	for(int ifor = 0; ifor < N; ifor++)
		cout << a[ifor] << " ";
	//int a[N] = {9, 19, 15, 13, 13, 9, 14, 1, 1, 7}
	int m = 3;
	cout << endl;
	//求出数列中所有数的和,还要求出当中最小的数min
	int min = INF, max = 0;
	for (int i = 0; i < N && a[i] != ' '; i++)
	{
		max += a[i];
		if (a[i] < min)
			min = a[i];
	}
	cout << endl;
	int tem = value(a, min, max, m);
	cout << tem << endl;
	return 0;
}


  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值