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;
}