分治法解最大子序列和、循环日程安排问题

本文深入解析分治法在解决最大子序列和问题及循环赛日程安排中的应用。通过实例代码展示了如何求解最大连续子序列的和,并设计满足特定条件的网球循环赛日程表。

上一篇文章告诉你分治法及它的简单使用

最大子序列和

问题描述

给定一个有 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>1,采用分治法求解最大连续子序列时,取其中间位置 mid=(n-
1)/2, 该子序列只可能出现3个地方。

  • 该子序列完全落在左半部即 a[0, …, mid] 中。采用递归求出其最大
    连续子序列和 leftMaxSum.
  • 该子序列完全落在右半部即 a[mid + 1, …, n - 1] 中。采用递归求出其最大连续子序列和 rightMaxSum.
  • 该子序列跨越序列a的中部而占据左右两部分。

算法实现

#include <iostream>
using namespace std;

int max3(int a, int b, int c);
int maxsubSum(int a[], int low, int high);

int main(){
    int a[] = {11,-4,13, -5,-2};
    cout << maxsubSum(a, 0, 4);
    return 0;
}

int max3(int a, int b, int c){
    return a > b ? (a > c ? a : c) : (b > c ? b : c);
}

int maxsubSum(int a[], int low, int high){
    if(low == high)
        if(a[low] > 0)
            return a[low];
        else
            return 0;

    // 递归求解
    int mid = (low + high) / 2;
    int leftMaxSum = maxsubSum(a, low, mid);   //左部最大和
    int rightMaxSum = maxsubSum(a, mid + 1, high);   //右部最大和

     // 从中间开始,向两边分别计算最大和并相加得出 low 和 high 之间的最大和
    int leftMarginMaxSum = 0;
    int rightMarginMaxSum = 0;
    int leftMarginSum = 0;
    int rightMarginSum = 0;
    for(int i = mid; i >= low; i--){
        leftMarginSum += a[i];
        if(leftMarginMaxSum < leftMarginSum)
            leftMarginMaxSum = leftMarginSum;
    }
    for(int i = mid + 1; i <= high; i++){
        rightMarginSum += a[i];
        if(rightMarginMaxSum < rightMarginSum)
            rightMarginMaxSum = rightMarginSum;
    }
    int marginMaxSum = leftMarginSum + rightMarginSum;

    return max3(leftMaxSum, rightMaxSum, marginMaxSum);
}

个人想法:

marginMaxSum = leftMarginSum + rightMarginSum

求 " 整个部分 " 的子序列最大和应该算是关键了。



循环日程安排

问题描述

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

分治策略

引子

  • k = 1
    计划表
选手号
12
21
* k = 2 计划表
选手号
1234
2143
3412
4321
  • k = 3
    计划表
    选手号
    12345678
    21436587
    34127856
    43218765
    56781234
    65872143
    78563412
    87654321
从上面可以了解到,日程表可以看作是一个 2 k * 2 k 的表:

1、k = 1 时的日程表是 k = 2 时日程表的左上部分,k = 2 时的日程表是 k = 3 时日程表的左上部分,····,k = n - 1 是的日程表是 k = n 时的日程表的左上部分
2、每个日程表的左上部分与右下部份相同,左下部分与右上部份相同
3、左上部分与左下部分对应位置的值相差 2k-1

基于这些特点,给出分治策略:

分治策略

将 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 个选手在后半程的比赛日程。

算法

#include <iostream>
using namespace std;
#define MAX 100

int n;   //比赛人数
int a[MAX][MAX];   //存放比赛日程表(行列下标为0的元素不用

// k 为2的幂数
void plan(int k){
    n = 2;   // n 从 2 ^ 1 = 2 开始

    a[1][1] = 1;
    a[1][2] = 2;
    a[2][1] = 2;
    a[2][2] = 1;

    int t = 1;
    while(t < k){
        int temp = n;
        n = n * 2;
        // 左下角
        for(int r = temp + 1; r <= n; r++){
            for(int c = 1; c <= temp; c++)
                a[r][c] = a[r - temp][c] + temp;
        }
        //右上角
        for(int r = 1; r <= temp; r++){
            for(int c = temp + 1; c <= n; c++)
                a[r][c] = a[r + temp][(c + temp) % n];
        }
        //右下角
        for(int r = temp + 1; r <= n; r++){
            for(int c = temp + 1; c <= n; c++)
                a[r][c] = a[r - temp][c - temp];
        }

        t++;
    }
}

int main(){
    plan(3);

    //结果展示
    for(int r = 1; r <= n; r++) {
        for (int c = 1; c <= n; c++)
            cout << a[r][c] << " ";
        cout << endl;
    }
    return 0;
}

个人想法

理解右上角元素的求法可能有些难度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值