基础算法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;
}
网上关于这道题的代码一大票退火模拟算法...不知道被劝退多少次的萌旧。

浙公网安备 33010602011771号