zoukankan      html  css  js  c++  java
  • 分治法题目整理分析 找第k小的数/求逆序对数目/派

    设计一个平均时间为O(n)的算法,在n(1<=n<=1000)个无序的整数中找出第k小的数。

    提示:函数int partition(int a[],int left,int right)的功能是根据a[left]~a[right]中的某个元素x(如a[left])对a[left]~a[right]进行划分,划分后的x所在位置的左段全小于等于x,右段全大于等于x,同时利用x所在的位置还可以计算出x是这批数据按升非降序排列的第几个数。因此可以编制int find(int a[],int left,int right,int k)函数,通过调用partition函数获得划分点,判断划分点是否第k小,若不是,递归调用find函数继续在左段或右段查找。

    输入格式:

    输入有两行:

    第一行是n和k,0<k<=n<=10000

    第二行是n个整数

    输出格式:

    输出第k小的数

    输入样例:

    在这里给出一组输入。例如:

    10 4
    2 8 9 0 1 3 6 7 8 2
    

    输出样例:

    在这里给出相应的输出。例如:

    2


    主要思路:根据提示我们能想到partition函数应该是快排的算法,返回index就是按升非降序排列的第几个数,如果index > k,说明找的这个数大于要找的第k大的数,递归调用find从l到index找;如果index < k,说明找的这个数小于要找的第k大的数,递归调用find从index到r找。

    破题点:快排算法
    //找第k小的数
    #include <iostream>
    using namespace std;
    
    int partition(int a[], int left, int right)
    {//将数组a的第left到right个元素进行划分
        int x = a[left];
    
        while (left < right)
        {//采用快排策略
            while (left < right && a[right] >= x)
                right--;
            a[left] = a[right];
    
            while (left < right && a[left] <= x)
                left++;
            a[right] = a[left];
        }
    
        a[left] = x;
    
        return left;
    }
    
    int find(int a[], int left, int right, int k)
    {//在数组a的第left到right中寻找第k小的数
        int pos = partition(a, left, right);
    
        if (k - 1 == pos)
            cout << a[k - 1];
        else if (k - 1 < pos)//判断下一次划分在哪一区间进行
            find(a, left, pos - 1, k);
        else
            find(a, pos + 1, right, k);
    
        return 0;
    
    }
    
    int main()
    {
        int n, k;
        cin >> n >> k;
    
        int a[1000];
        for (int i = 0; i < n; i++)
            cin >> a[i];
        int i;
        i = find(a, 0, n - 1, k);
    
        return 0;
    }

    分割线

    注意:本问题算法的时间复杂度要求为O(nlogn), 否则得分无效

    题目来源:http://poj.org/problem?id=1804 Background Raymond Babbitt drives his brother Charlie mad. Recently Raymond counted 246 toothpicks spilled all over the floor in an instant just by glancing at them. And he can even count Poker cards. Charlie would love to be able to do cool things like that, too. He wants to beat his brother in a similar task.

    Problem Here's what Charlie thinks of. Imagine you get a sequence of N numbers. The goal is to move the numbers around so that at the end the sequence is ordered. The only operation allowed is to swap two adjacent numbers. Let us try an example: Start with: 2 8 0 3 swap (2 8) 8 2 0 3 swap (2 0) 8 0 2 3 swap (2 3) 8 0 3 2 swap (8 0) 0 8 3 2 swap (8 3) 0 3 8 2 swap (8 2) 0 3 2 8 swap (3 2) 0 2 3 8 swap (3 8) 0 2 8 3 swap (8 3) 0 2 3 8

    So the sequence (2 8 0 3) can be sorted with nine swaps of adjacent numbers. However, it is even possible to sort it with three such swaps: Start with: 2 8 0 3 swap (8 0) 2 0 8 3 swap (2 0) 0 2 8 3 swap (8 3) 0 2 3 8

    The question is: What is the minimum number of swaps of adjacent numbers to sort a given sequence?Since Charlie does not have Raymond's mental capabilities, he decides to cheat. Here is where you come into play. He asks you to write a computer program for him that answers the question in O(nlogn). Rest assured he will pay a very good prize for it.

    输入格式:

    The first line contains the length N (1 <= N <= 1000) of the sequence; The second line contains the N elements of the sequence (each element is an integer in [-1000000, 1000000]). All numbers in this line are separated by single blanks.

    输出格式:

    Print a single line containing the minimal number of swaps of adjacent numbers that are necessary to sort the given sequence.

    输入样例:

    在这里给出一组输入。例如:

    6
    -42 23 6 28 -100 65537
    

    输出样例:

    在这里给出相应的输出。例如:

    5

    分析:这题目是求最小逆序对数,从最小逆序对数能想到的是归并排序,归并排序实质是从两个数两个数之间开始进行调换调整,再逐层从底向上进行合并,那么只要记录下合并过程中交换的次数就好了
    #include <iostream>
    using namespace std;
    
    //将两个有序数列a,b合并
    
    int cnt = 0;
    #include <iostream>
    using namespace std;
    
    //将有二个有序数列a[first...mid]和a[mid...last]合并。
    void mergearray(int a[], int first, int mid, int last, int temp[])
    {
        int i = first, j = mid + 1;
        int m = mid, n = last;
        int k = 0;
    
        
        while (i <= m && j <= n)
        {
            if (a[i] <= a[j])
            {
                temp[k++] = a[i++];
                
            }
                
            else
            {
                temp[k++] = a[j++];
                cnt += mid - i + 1;//mid - i + 1就是交换的次数 往前换
            }
        }
    
        while (i <= m)
            temp[k++] = a[i++];
    
        while (j <= n)
            temp[k++] = a[j++];
    
        for (i = 0; i < k; i++)
            a[first + i] = temp[i];
    }
    
    void mergesort(int a[], int first, int last, int temp[])
    {
        if (first < last)
        {
            int mid = (first + last) / 2;
            mergesort(a, first, mid, temp);    //左边有序
            mergesort(a, mid + 1, last, temp); //右边有序
            mergearray(a, first, mid, last, temp); //再将二个有序数列合并
        }
    }
    
    bool MergeSort(int a[], int n)
    {
        int *p = new int[n];
        if (p == NULL)
            return false;
        mergesort(a, 0, n - 1, p);
        delete[] p;
        return true;
    }
    int main()
    {    
        int n;
        cin >> n;
        int a[10000];
        for (int i = 0; i < n; i++)
        {
            cin >> a[i];
        }
    
        MergeSort(a, n);
        /*
        for (int i = 0; i < sizeof(a) / sizeof(int); i++)
        {
    
            cout << a[i] << endl;
    
        }
        */
        cout << ::cnt;
    }

    分割线

    我的生日要到了!根据习俗,我需要将一些派分给大家。我有N个不同口味、不同大小的派。有F个朋友会来参加我的派对,每个人会拿到一块派(必须一个派的一块,不能由几个派的小块拼成;可以是一整个派)。

    我的朋友们都特别小气,如果有人拿到更大的一块,就会开始抱怨。因此所有人拿到的派是同样大小的(但不需要是同样形状的),虽然这样有些派会被浪费,但总比搞砸整个派对好。当然,我也要给自己留一块,而这一块也要和其他人的同样大小。

    请问我们每个人拿到的派最大是多少?每个派都是一个高为1,半径不等的圆柱体。

    输入格式:

    第一行包含两个正整数N和F,1 ≤ N, F ≤ 10 000,表示派的数量和朋友的数量。 第二行包含N个1到10000之间的整数,表示每个派的半径。

    输出格式:

    输出每个人能得到的最大的派的体积,精确到小数点后三位。

    输入样例:

    3 3
    4 3 3
    

    输出样例:

    在这里给出相应的输出。例如:

    25.133


    思路:用二分遍历查找,其实就是一种枚举的思想,最终找到拿到的派最大的大小。

    #include <stdlib.h>
    #include <stdio.h>
    #include <iostream>
    
    using namespace std;
    #define PI 3.141592653589793
    
    int n, friendNum; //n N个不同口味、不同大小的派。 friendNum 小朋友数量
    int a[10001];//记录每个派的半径
    double areaOfCake[10001];
    
    int isOK(double res)
    {
        int i, sum = 0;
        for (i = 1; i <= n; i++)
        {
            sum += int(areaOfCake[i] / res);//  int(areaOfCake[i] / res) : 就是看能分成几块,这里巧妙的用了int除法
        }
        if (sum >= friendNum)//如果块数大过小朋友数量说明够分,继续向大逼近
            return 1;
        else return 0;//不过的话就像小去逼近
    }
    
    int main()
    {
        int i;
        double max, left, right;
        double ans;
        //读取数据
        cin >> n >> friendNum;
        friendNum++;
        max = 0;
        for (i = 1; i <= n; i++)
        {
            cin >> a[i];
            areaOfCake[i] = a[i] * a[i] * PI;
    
            if (areaOfCake[i] > max) {
                max = areaOfCake[i];
            }
        }
        //二分遍历查找
        left = 0;
        right = max;
        while (right - left > 1e-5)
        {
            double mid = (left + right) / 2;
    
            if (isOK(mid)) { //去逼近最大值
                left = mid;
            }
            else {
                right = mid;
            }
        }
    
        if (isOK(left))
            ans = left;
    
    
        printf("%.3f", ans);
    
    }


  • 相关阅读:
    Docker概念学习系列之详谈Docker 的核心组件与概念(5)
    全网最详细的如何在谷歌浏览器里正确下载并安装Postman【一款功能强大的网页调试与发送网页HTTP请求的Chrome插件】(图文详解)
    全网最详细的一款满足多台电脑共用一个鼠标和键盘的工具Synergy(图文详解)
    [转]华为开发者联盟开放的服务
    [转]英语发音规则---E字母常见的发音组合有哪些
    [转]【信息系统项目管理师】重点整理:高项知识地图
    [转]【信息系统项目管理师】高项案例分析攻略
    [转]【信息系统项目管理师】案例分析记忆题
    [转]850 Basic English words
    [转]信息系统项目管理师计算题汇总
  • 原文地址:https://www.cnblogs.com/likeghee/p/11756204.html
Copyright © 2011-2022 走看看