分治法题目整理分析 找第k小的数/求逆序对数目/派

Posted likeghee

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分治法题目整理分析 找第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

分析:这题目是求最小逆序对数,从最小逆序对数能想到的是归并排序,归并排序实质是从1/2个数开始进行调换调整,再逐层从底向上进行合并,那么只要记录下交换的次数就好了
#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);

}

 



以上是关于分治法题目整理分析 找第k小的数/求逆序对数目/派的主要内容,如果未能解决你的问题,请参考以下文章

XJTUOJ wmq的队伍(树状数组求 K 元逆序对)

算法设计第二章总结

codevs 4163 求逆序对的数目 -树状数组法

cogs930.[河南省队2012] 找第k小的数

[河南省队2012] 找第k小的数

COGS 930. [河南省队2012] 找第k小的数