分治法求解组合问题

求解最大连续子序列和问题

问题描述

给定一个有n(n≥1)个整数的序列,要求求出其中最大连续子序列的和。
例如:
序列(-2,11,-4,13,-5,-2)的最大子序列和为20
序列(-6,2,4,-7,5,3,2,-1,6,-9,10,-2)的最大子序列和为16。
规定一个序列最大连续子序列和至少是0(长度为0的子序列),如果小于0,其结果为0。

问题求解

对于含有n个整数的序列a[0…n-1],若n=1,表示该序列仅含一个元素,如果该元素大于0,则返回该元素;否则返回0。
若n>1,采用分治法求解最大连续子序列时,取其中间位置mid=(n-1)/2,该子序列只可能出现3个地方。
(1)该子序列完全落在左半部即a[0…mid]中。采用递归求出其最大连续子序列和maxLeftSum。
(2)该子序列完全落在右半部即a[mid+1…n-1]中。采用递归求出其最大连续子序列和maxRightSum。
(3)该子序列跨越序列a的中部而占据左右两部分。

结果:max3( maxLeftSum,maxRightSum,maxLeftBorderSum+maxRightBorderSum )

代码

long maxSubSum(int a[], int left, int right)
{
	int i, j;
	long maxLeftSum, maxRightSum;
	long maxLeftBorderSum, leftBoerderSum;
	long maxRightBorderSum, rightBorderSum;

	if (left == right)//序列中只有一个元素
	{
		if (a[left] > 0)
			return a[left];
		else
			return 0;
	}

	int mid = (left + right) / 2;
	maxLeftSum = maxSubSum(a, left, mid);//左区间最大连续子序列和
	maxRightSum = maxSubSum(a, mid + 1, right);//右区间最大连续子序列和

	maxLeftBorderSum = 0, leftBoerderSum = 0;
	for (i = mid; i >= left; i--)//求解左边界最大连续子序列和
	{
		leftBoerderSum += a[i];
		if (leftBoerderSum > maxLeftBorderSum)
			maxLeftBorderSum = leftBoerderSum;
	}

	maxRightBorderSum = 0, rightBorderSum = 0;
	for (j = mid + 1; j <= right; j++)//求解右边界最大连续子序列和
	{
		rightBorderSum += a[j];
		if (rightBorderSum > maxRightBorderSum)
			maxRightBorderSum = rightBorderSum;
	}

	return (maxLeftBorderSum + maxRightBorderSum) > max(maxLeftSum, maxRightSum) ? (maxLeftBorderSum + maxRightBorderSum) : max(maxLeftSum, maxRightSum);//返回最大值
}

算法分析

设求解序列a[0…n-1]最大连续子序列和的执行时间为T(n),第(1)、(2)两种情况的执行时间为T(n/2),第(3)种情况的执行时间为O(n),所以得到以下递推式:
T(n)=1 当n=1
T(n)=2T(n/2)+n 当n>1
容易推出,T(n)=O(nlog2n)。

求解棋盘覆盖问题

问题描述

有一个2k×2k(k>0)的棋盘,恰好有一个方格与其他方格不同,称之为特殊方格。现在要用如下的L型骨牌覆盖除了特殊方格外的其他全部方格,骨牌可以任意旋转,并且任何两个骨牌不能重叠。请给出一种覆盖方法。

问题求解

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

代码

int k;//棋盘大小
int x, y;//特殊方格的位置
int board[MAX][MAX];
int tile = 1;

void ChessBoard(int tr, int tc, int dr, int dc, int size)
{
	if (size == 1)
		return;
	int t = tile++;
	int s = size / 2;

	//考虑左上角象限
	if (dr < tr + s && dc < tc + s)//特殊方格位于左上角
		ChessBoard(tr, tc, dr, dc, s);
	else//不位于左上角
	{
		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);//将右上角方格作为特殊方格
	}
}

求解循环日程安排问题

问题描述

设有n=2k个选手要进行网球循环赛,要求设计一个满足以下要求的比赛日程表:
(1)每个选手必须与其他n-1个选手各赛一次。
(2)每个选手一天只能赛一次。
(3)循环赛在n-1天之内结束。

问题求解

按问题要求可将比赛日程表设计成一个n行n-1列的二维表,其中第i行、第j列表示和第i个选手在第j天比赛的选手。
假设n位选手被顺序编号为1、2、…、n(2k)。
在这里插入图片描述
在这里插入图片描述
将n=2k问题划分为4部分:
(1)左上角:左上角为2k-1个选手在前半程的比赛日程(k=1时直接给出,否则,上一轮求出的就是2k-1个选手的比赛日程)。
(2)左下角:左下角为另2k-1个选手在前半程的比赛日程,由左上角加2k-1得到,例如22个选手比赛,左下角由左上角直接加2(2k-1)得到,23个选手比赛,左下角由左上角直接加4(2k-1)得到。
(3)右上角:将左下角直接复制到右上角得到另2k-1个选手在后半程的比赛日程。
(4)右下角:将左上角直接复制到右下角得到2k-1个选手在后半程的比赛日程。

代码

int k;//求解结果标识
int a[MAX][MAX];//存放比赛日程表
void Plan(int k)
{
	int i, j, n, t, temp;
	n = 2;
	a[1][1] = 1; a[1][2] = 2;
	a[2][1] = 2; a[2][2] = 1;

	for (t = 1; t < k; t++)
	{
		temp = n;
		n = n * 2;

		for (i = temp + 1; i <= n; i++)//填写左下角元素
			for (j = 1; j <= temp; j++)
				a[i][j] = a[i - temp][j] + temp;

		for (i = 1; i <= temp; i++)//填写右上角元素
			for (j = temp + 1; j <= n; j++)
				a[i][j] = a[i + temp][(j + temp) % n];

		for (i = temp + 1; i <= n; i++)//填写右下角元素
			for (j = temp + 1; j <= n; j++)
				a[i][j] = a[i - temp][j - temp];
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值