分治的基本思路:
(1)划分问题:把问题的实际化为子问题;
(2)递归求解:递归解决子问题;
(2)合并问题:合并子问题得解.
举例:
1.给定一个数列,其中可能有正数也可能有负数,找出其中连续的一个子数列(不允许空序列),使它们的和尽可能大。
解法:
我们可以把整个序列平均分成左右两部分,答案则会在以下三种情况中:
1、所求序列完全包含在左半部分的序列中。
2、所求序列完全包含在右半部分的序列中。
3、所求序列刚好横跨分割点,即左右序列各占一部分。
前两种情况和大问题一样,只是规模小了些,如果三个子问题都能解决,那么答案就是三个结果的最大值。我们主要研究一下第三种情况如何解决:我们只要计算出:以分割点为起点向左的最大连续序列和、以分割点为起点向右的最大连续序列和,这两个结果的和就是第三种情况的答案。因为已知起点,所以这两个结果都能在O(N)的时间复杂度能算出来。
递归不断减小问题的规模,直到序列长度为1的时候,那答案就是序列中那个数字。
#include <bits/stdc++.h>
using namespace std;
// 区间左闭右开
int maxSum(int *A, int x, int y) {
int m = x+(y-x)/2; // 分成两部分
if(y-x == 1) return A[x]; // 如果只有一个数直接返回
int ans = max(maxSum(A, x, m), maxSum(A, m, y)); // 全部存在于左边和全部存在于右边的情况取最大
//cout << "debug: " << x << "&" << y << " # "<< ans << endl;
int v, l, r;
// 从分界点往左的最大和
v = 0, l = A[m-1];
for (int i = m-1; i >= 0; i--) {
l = max(l, v += A[i]);
}
// 从分界点往右的最大和
v = 0, r = A[m];
for (int i = m; i < y; i++) {
r = max(r, v += A[i]);
}
ans = max(ans, l+r);
return ans;
}
int main() {
int n;
int A[105];
cin >> n;
for(int i = 0; i < n; i++) cin >> A[i];
cout << maxSum(A, 0, n);
return 0;
}
其他解法
(1) O(n^3)(暴力)
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, sum = 0, ans;
int a[1000];
cin >> n;
for(int i = 0; i < n; i++) {
cin >> a[i];
}
ans = a[0];
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
sum = 0;
for (int k = i; k <= j; ++k) {
sum += a[k];
}
ans = max(ans, sum);
}
}
cout << ans << endl;
return 0;
}
(2) O(n^2) (前缀和)
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, ans;
cin >> n;
int a[n+5], b[n+5];
for(int i = 0; i < n; i++) {
cin >> a[i];
}
b[0] = a[0];
for (int i = 1; i < n; ++i) {
b[i] = b[i-1] + a[i];
}
ans = a[0];
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
ans = max(ans, b[j] - b[i] + a[i]);
}
}
cout << ans << endl;
return 0;
}
// 1 2 3 4 5
//
// 2 4
(3) O(n)(dp)
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, ans;
cin >> n;
int a[n+5], dp[n+5];
for(int i = 0; i < n; i++) {
cin >> a[i];
}
dp[0] = a[0];
ans = a[0];
for (int i = 1; i < n; ++i) {
if(dp[i-1] > 0) dp[i] = dp[i-1] + a[i];
else dp[i] = a[i];
ans = max(dp[i], ans);
}
cout << ans << endl;
return 0;
}
/*
8
4 -3 5 -2 -1 2 6 -2
* */
2.全排列
参考博文:https://blog.csdn.net/whealther/article/details/48878829
当n=1时,集合中只有一个元素;当n>1时,将整个数列中每一个数分别与第一个数交换后加其他数的全排列,因此每次都是处理后n-1个数的全排列(递归)
ps.必须交换两次, 交换完了后要交换回来,因为后面交换都是在原来的数列1,2,3...的基础上交换的,所以在排列前要交换一次,排列后还要交换一次(还原)
代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
int a[105];
void perm(int k, int m) {
if(k == m) { // 只剩一个元素
for(int i = 1; i <= m; i++) {
printf("%d", a[i]);
if(i != m)
printf(" ");
}
printf("
");
return;
}
for(int i = k; i <= m; i++) {
swap(a[i], a[k]);
perm(k+1, m); // 相当于以当前元素为首的全排列
swap(a[i], a[k]);
}
}
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {a[i] = i;}
perm(1, n);
return 0;
}
3.整数划分
#include <bits/stdc++.h>
using namespace std;
#define ll long long
ll dp[100][100];
// 函数表示正整数n可以划分为不大于m的数的方法数
ll solve(int n, int m) {
if(n < 1 || m < 1) return 0;
if(dp[n][m]>0) return dp[n][m];
// m或者n为一,只有一种情况
if(n == 1 || m == 1) dp[n][m] = 1;
// m > n与m=n相同
if(m > n) dp[n][m] = solve(n, n);
// m=n时,用n组成m只有一种情况,在加上小于m的所有情况
if((n != 1 && m != 1) && m == n) dp[n][m] = solve(n, m-1)+1;
// m < n,emmmmmm看几个样例体会一下...
/*
6;
5+1;
4+2,4+1+1;
3+3,3+2+1,3+1+1+1;
2+2+2,2+2+1+1,2+1+1+1+1;
1+1+1+1+1+1。
*/
if(m < n) dp[n][m] = solve(n, m-1) + solve(n-m, m);
return dp[n][m];
}
int main() {
int n;
while(~scanf("%d", &n)) {
memset(dp, 0, sizeof dp);
solve(n, n);
printf("%lld
", dp[n][n]);
}
return 0;
}