基础算法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;
}

  网上关于这道题的代码一大票退火模拟算法...不知道被劝退多少次的萌旧。

posted @ 2021-06-01 23:02  anyiya  阅读(49)  评论(0编辑  收藏  举报