基础算法2.3——分治法
题目上添加了超链接,大家点一下题目就会自动跳转到Poj原题界面~~ 冲鸭冲鸭ヾ(◍°∇°◍)ノ゙。
前言:
分治是在递归思想之上的晋级,将问题分解成很多个相同的小问题,用同样的代码块求解这些问题,最后将解合并。归并排序是分治非常经典的应用。这里开篇就用经典的归并排序为大家引入分治法吧!
#include <iostream> using namespace std; int a[10005], b[10005]; void merge(int l, int m, int r) //归并 { int i = l, j = m + 1, k = 1; while (i <= m && j <= r) { if (a[i] > a[j]) //N=N+m-i+1;如果需要记录逆序对就要在这里 b[k] = a[j++]; else b[k] = a[i++]; k++; } while (i <= m) //余下的a[i..m]给b[k..k+q-i]; b[k++] = a[i++]; while (j <= r) //余下的a[j..r]给b[k..k+j-r]; b[k++] = a[j++]; for (i = 1; i <= k; i++) //把有序的b[1..k]还给a[l..r] a[l++] = b[i]; } void mersort(int l, int r) //归并排序 { if (l < r) { int m = (l + r) / 2; mersort(l, m); mersort(m + 1, r); merge(l, m, r); } } int main() { int n; cin >> n; for (int i = 1; i <= n; i++) cin >> a[i]; mersort(1, n); for (int i = 1; i <= n; i++) cout << a[i] << " "; return 0; }
这是特意添加了注释的代码,推荐大家运行一下,过程就能看的很明白。萌新手写代码,如有问题欢迎大家指正~~
给出一组打印出的测试:
8
8 4 1 3 7 9 2 6
区间:1 - - 2 区间:3 - - 4 区间:1 - - 4 区间:5 - - 6 区间:7 - - 8 区间:5 - - 8
归并前:8 4 归并前:1 3 归并前:4 8 1 3 归并前:7 9 归并前:2 6 归并前:7 9 2 6
归并后:4 8 归并后:1 3 归并后:1 3 4 8 归并后:7 9 归并后:2 6 归并后:2 6 7 9
区间:1 - - 8
归并前:1 3 4 8 2 6 7 9
归并后:1 2 3 4 6 7 8 9
1 2 3 4 6 7 8 9
写好了归并干什么,再随手写一个快排吧。手动\拍桌
#include <iostream> using namespace std; int a[100005]; void quisort(int l, int r) { if (l >= r) return; int i = l, j = r, key = a[l]; while (i < j) { while (i < j && a[j] >= key) j--; a[i] = a[j]; while (i < j && a[i] <= key) i++; a[j] = a[i]; } a[i] = key; quisort(l, i - 1); quisort(i + 1, r); } int main() { int n; cin >> n; for (int i = 1; i <= n; i++) cin >> a[i]; quisort(1, n); for (int i = 1; i <= n; i++) cout << a[i] << " "; return 0; }
这两个经典应用大家认识之后想必就能对分治有了一定了解,为防止劝退(来自总是每本书前两章翻烂,从第三章开始就不知被劝退了多少次的萌旧)。这里预警一下:本章节开始部分题目上了难度,大家做好心理准备!不要走,一起感受算法精髓与优雅!
2.3.1 Who's in the Middle (2388)
题意:求n个数的中位数,n为奇数。
小笔记:<algorithm>下存在现成的健壮快排sort函数,本题白给,建议大家手动实现。
#include <cstdio> #include <algorithm> using namespace std; int a[10005]; //对数组进行重排,将整个数组通过i位置划分为左右两个数组 int partition(int left, int right) { int i = left; for (int j = left; j < right; j++) { if (a[j] < a[right]) { swap(a[j], a[i]); i++; } } swap(a[i], a[right]); return i; } //递归对左右两部分分别进行快速排序 void quickSort(int left, int right) { if (left < right) { int i = partition(left, right); quickSort(left, i - 1); quickSort(i + 1, right); } } int main() { int n; scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", &a[i]); quickSort(1, n); printf("%d\n", a[(n + 1) / 2]); return 0; }
2.3.2 Ultra-QuickSort (2299)
题意:求逆序对。
#include <cstdio> using namespace std; const int N = 500005; int a[N], b[N]; long long cnt; //所求的交换次数(逆序对个数) void merge(int left, int mid, int right) { int i = left; // i指向a[left…mid]最左侧元素 int j = mid + 1; // j指向a[mid+1…right]最左侧元素 int k = 0; //k指向b数组将要写入元素的位置 while (i <= mid && j <= right) { if (a[i] > a[j]) { b[k++] = a[j++]; cnt += mid - i + 1; } else b[k++] = a[i++]; } while (i <= mid) b[k++] = a[i++]; while (j <= right) b[k++] = a[j++]; while (k--) a[left + k] = b[k]; } void mergeSort(int left, int right) { if (left < right) { int mid = (left + right) / 2; mergeSort(left, mid); mergeSort(mid + 1, right); merge(left, mid, right); } } int main() { int n; while (scanf("%d", &n) && n) { for (int i = 0; i < n; i++) scanf("%d", &a[i]); cnt = 0; mergeSort(0, n - 1); printf("%lld\n", cnt); } return 0; }
2.3.3 Aggressive cows (2456)
题意:n个牛棚水平排列在位置x[0…n-1],将m头牛安排到牛棚里,使所有牛之间的距离的最小值尽可能的大,求这个最大的最小距离是多少。
小笔记:Farmer John and cows初登场。。代码样例是用了折半对枚举进行优化,本质还是枚举在尝试。
#include <cstdio> #include <algorithm> using namespace std; const int N = 100001; int x[N], n; //search函数,从前往后找满足距离大于a的位置,数量记为s,i初始为0,找到大于a的距离之后,将i移动到该点继续向后寻找 int search(int a) { int j, s = 1; for (int i = 0; i < n; i = j) for (j = i + 1; j < n; j++) if (x[j] - x[i] >= a) { s++; break; } return s; } int main() { int m; scanf("%d%d", &n, &m); for (int i = 0; i < n; i++) scanf("%d", &x[i]); sort(x, x + n); int a = 0; //二分查找的区间左端点 int b = x[n - 1] - x[0]; //二分查找的区间右端点 while (b - a > 1) { int c = (a + b) / 2; //二分查找的区间中点 //查找满足c值的距离数量。如果数量超过m,则在c到b中继续查找;若不足m,则在a到c中继续查找 if (search(c) < m) b = c; else a = c; } printf("%d\n", a); return 0; }
2.3.4 Pie (3122)
题意:有n张馅饼,半径分别为ri,将馅饼切开分给f+1个人,要求每个人得到的馅饼切块的体积相同并且只能来自其中一张馅饼。求每人能得到的最大切块馅饼体积是多少。
小笔记:初见这题时读了好久,啊,每人分得的馅饼只能来自一张馅饼,原来有浪费...这是我们遇见的第一个要不断尝试浮点数的问题,要用二分查找。
#include <cstdio> #include <cmath> #include <cfloat> using namespace std; const int N = 10001; const double PI = acos(-1.0); const int M = N * N * PI; //定义最大的馅饼体积 int r[N], n; // x是枚举的馅饼切块体积,按照体积x将n个馅饼共分为s块 double search(double x) { int s = 0; // 累加 等面积的馅饼块数 for (int i = 0; i < n; i++) s += r[i] * r[i] * PI / x; return s; } int main() { int t, f; scanf("%d", &t); while (t--) { scanf("%d%d", &n, &f); for (int i = 0; i < n; i++) scanf("%d", &r[i]); double a = 0; double b = M; //用计算出来的总块数值和人数f+1作比较,当两者差小于FLT_EPSILON即可终止 while ((b - a) > FLT_EPSILON) { double c = (a + b) * 0.5; if (search(c) < f + 1) b = c; else a = c; } printf("%.4f\n", a); } return 0; }
2.3.5 Expanding Rods (1905)
题意:细棒长度L,两端固定,加热后膨胀为一段圆弧,长度变为L'=(1+n*C)*L,n为变化的温度,C为膨胀系数,求细棒中心移动的距离。
小笔记:被朋友放在新生赛上恶心过学弟学妹们,所以印象深刻。感觉现在实现起来也要费一番功夫(写不出来也要嘴硬)。先得出几何关系式,可以知道变化后的长度,然后不断尝试x得出最接近的L’。很经典的据果求因,逐步求精。
#include <cstdio> #include <cmath> #include <cfloat> using namespace std; double L; double f(double x) // 根据x计算L’的函数 { double R = (x * x + L * L / 4) / (2 * x); double a = asin(L / (2 * R)); return 2 * R * a; } int main() { double n, C; while (scanf("%lf%lf%lf", &L, &n, &C)) { if (L == -1 && n == -1 && C == -1) break; double L1 = (1 + n * C) * L; double a = 0; double b = L / 2; double c; while (b - a > FLT_EPSILON) { c = (a + b) / 2; if (f(c) >= L1) b = c; else a = c; } printf("%.3f\n", c); } return 0; }
2.3.6 A Star not a Tree? (2420)
题意:给出平面上n个点的坐标,找到一点,到这些点的距离和最小。(费马点、退火模拟)
小笔记:不清楚费马点的同学,应该有不少先入想法也是求所有点的中间值吧...问题区间不满足单调性,但符合凸函数性质,样例代码采用了三分搜索。
#include <cstdio> #include <cmath> using namespace std; double x[101], y[101]; int n; //计算某点(x1,y1)到其他点的距离和 double f(double x1, double y1) { double sum = 0; for (int i = 0; i < n; ++i) sum += sqrt((x1 - x[i]) * (x1 - x[i]) + (y1 - y[i]) * (y1 - y[i])); return sum; } //对点的y值进行三分搜索,找到每个固定点x1值对应的使距离和最小的y值 double fy(double x1) { double l = 0; double r = 10000; while (r - l > 0.1) { double lt = (l * 2 + r) / 3; double rt = (l + r * 2) / 3; f(x1, lt) < f(x1, rt) ? r = rt : l = lt; } return f(x1, l); } //对点的x值进行三分搜索,找到使距离和最小的x值 double fx() { double l = 0; double r = 10000; while (r - l > 0.1) { double lt = (l * 2 + r) / 3; double rt = (l + r * 2) / 3; fy(lt) < fy(rt) ? r = rt : l = lt; } return fy(l); } int main() { scanf("%d", &n); for (int i = 0; i < n; ++i) scanf("%lf%lf", &x[i], &y[i]); printf("%.0f\n", fx()); return 0; }
网上关于这道题的代码一大票退火模拟算法...不知道被劝退多少次的萌旧。