PTA 分治法

  文章目录

目录

一、函数题

1.二分查找

2. Iterative Mergesort

3.Quick Power

4.划分整数数组

二、编程题

1.古老的汉诺塔

2.士兵排队

3.输油管道问题

4.子序列的平均值

5.第k小元素

6.铺设油井管道

7.第k小


一、函数题

1.二分查找

本题要求实现二分查找算法。

函数接口定义:

Position BinarySearch( List L, ElementType X );

其中List结构定义如下:

typedef int Position; typedef struct LNode *List; struct LNode { ElementType Data[MAXSIZE]; Position Last; /* 保存线性表中最后一个元素的位置 */ };

L是用户传入的一个线性表,其中ElementType元素可以通过>、==、<进行比较,并且题目保证传入的数据是递增有序的。函数BinarySearch要查找XData中的位置,即数组下标(注意:元素从下标1开始存储)。找到则返回下标,否则返回一个特殊的失败标记NotFound

Position BinarySearch( List L, ElementType X )
{
    Position l=1,r=L->Last;
    while(l<r)
    {
        int mid=(l+r)>>1;
        if(L->Data[mid]>=X)r=mid;
        else l=mid+1;
    }
    if(L->Data[l]==X)return l;
    else return NotFound;
}

2. Iterative Mergesort

How would you implement mergesort without using recursion?

The idea of iterative mergesort is to start from N sorted sublists of length 1, and each time to merge a pair of adjacent sublists until one sorted list is obtained. You are supposed to implement the key function of merging.

Format of functions:

void merge_pass( ElementType list[], ElementType sorted[], int N, int length );

The function merge_pass performs one pass of the merge sort that merges adjacent pairs of sublists from list into sortedN is the number of elements in the list and length is the length of the sublists.

void merge_pass( ElementType list[], ElementType sorted[], int N, int length )
{
    for(int i=0;i<N;i+=2*length)
    {
        int num1=i,num2=i+length,num=i;
        int last=i+2*length;
        if(last>N)last=N;
        while(num1<i+length&&num2<last&&num1<N&&num2<N)
            if(list[num1]<list[num2])
                sorted[num++]=list[num1++];
            else
                sorted[num++]=list[num2++];
        while(num1<i+length)
            sorted[num++]=list[num1++];
        while(num2<last)
            sorted[num++]=list[num2++];
    }
}

3.Quick Power

The function Power calculates the exponential function Nk. But since the exponential function grows rapidly, you are supposed to return (Nk)%10007 instead.

Format of function:

int Power(int N, int k);

Both N and k are integers, which are no more than 2147483647.

int Power(int N, int k)
{
    int res=1;
    while(k)
    {
        if(k&1)res=(long long)res*N%10007;
        k>>=1;
        N=(long long)N*N%10007;
    }
    return res;
}

4.划分整数数组

由n个正整数构成的集合A{ak}, 将其划分为两个不相交的子集A1,A2, 元素个数分别是n1,n2, A1和A2中的元素之和分别是S1,S2。
设计一个尽可能高效的划分算法,满足|n1-n2|最小,且|S1-S2|最大,返回|S1-S2|的结果。
注意:函数头已经给出,只需要写出函数体。

函数接口定义:

int Partition(int a[],int n); // 将数组a的整数集合划分成两个不相关子集,使得|n1-n2|最小,且|S1-S2|最大,返回|S1-S2|的结果。
    int l=0,r=n-1,k=n/2;
    int i=l,j=r;
    while(1)
    {
        int x=a[i];
        while(i<j)
        {
            while(i<j&&a[j]>=x)j--;
            a[i]=a[j];
            while(i<j&&a[i]<=x)i++;
            a[j]=a[i];
        }
        a[i]=x;
        if(i==k-1)break;
        else if(i<k-1)l=++i,j=r;
        else r=--j,i=l;
    }
    int res1=0,res2=0;
    for(int i=0;i<k;i++)res1+=a[i];
    for(int i=k;i<n;i++)res2+=a[i];
    return res2-res1;


二、编程题

1.古老的汉诺塔

有三根A、B、C三根柱子,n个直径大小不同的圆盘,这些圆盘按照直径从大到小的次序从下到上放在A柱上,如果把1个圆盘从从一根柱子移动到另外一根柱子称作1次移动,在移动过程中允许借用B柱子,但不允许大圆盘放在小圆盘上面。现在要用最少的步数把这些圆盘全部移到C柱,请设计一个算法求出移动步骤和移动次数。

hanoi.png

输入格式:

一个整数n(1≤n≤15),表示圆盘的个数。

输出格式:

输出移动盘子的过程和总的移动次数,先输出移动步骤,每一步占一行,格式为:当前要移动的盘子所在的柱子编号-->移动后所在柱子的编号。最后一行是总的移动次数。

#include<iostream>
using namespace std;
int num=0;
void move(char first,char second)
{
    cout<<first<<"-->"<<second<<endl;
    num++;
}
void hanoi(int n,char left,char mid,char right)
{
    if(n==1)
        move(left,right);
    else
    {
        hanoi(n-1,left,right,mid);
        move(left,right);
        hanoi(n-1,mid,left,right);
    }
}
int main()
{
    int n;cin>>n;
    hanoi(n,'A','B','C');
    cout<<num;
    return 0;
}


 2.士兵排队

在一个划分成网格的操场上,n个士兵散乱地站在网格点上。网格点用整数坐标(x,y)表示。士兵们可以沿网格边往上、下、左、右移动一步,但在同一时刻任一网格点上只能有一名士兵。按照军官的命令,士兵们要整齐地列成一个水平队列,即排列成(x,y),(x+1,y),…,(x+n-1,y)。如何选择x和y的值才能使士兵们以最少的总移动步数排成一行。

编程计算使所有士兵排成一行需要的最少移动步数。

题目引自POJ

输入格式:

第1行是士兵数n,1≤n≤10000。接下来n行是士兵的初始位置,每行有2个整数x和y,-10000≤x,y≤10000。

输出格式:

一个数据,即士兵排成一行需要的最少移动步数。

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;

int x[N],y[N];
int t,n;

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        scanf("%d%d",&x[i],&y[i]);
    sort(x+1,x+n+1);
    for(int i=1;i<=n;i++)
        x[i]-=i;
    sort(x+1,x+n+1);
    sort(y+1,y+n+1);
    int resx,resy;
    if(n%2)resx=x[n/2+1],resy=y[n/2+1];
    else resx=(x[n/2+1]+x[n/2])/2,resy=(y[n/2+1]+y[n/2])/2;
    long long ans=0;
    for(int i=1;i<=n;i++)
        ans+=abs(y[i]-resy)+abs(x[i]-resx);
    cout<<ans;
    return 0;
}

3.输油管道问题

某石油公司计划建造一条由东向西的主输油管道。该管道要穿过一个有n 口油井的油田。从每口油井都要有一条输油管道沿最短路经(或南或北)与主管道相连。如果给定n口油井的位置,即它们的x 坐标(东西向)和y 坐标(南北向),应如何确定主管道的最优位置,即使各油井到主管道之间的输油管道长度总和最小的位置? 证明可在线性时间内确定主管道的最优位置。

给定n口油井的位置, 计算各油井到主管道之间的输油管道最小长度总和。

输入格式:

输入的第1 行是油井数n,1<=n<=10000。接下来n 行是
油井的位置,每行2个用空格割开的整数 x和 y,-10000<=x,y<=10000。

输出格式:

输出油井到主管道之间的输油管道最小长度总和。

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;

int a[N];
int t,n;
int findk(int a[],int l,int r,int k)
{
    if(l==r)
        return a[l];
    int i=l-1,j=r+1;
    int x=a[l+r>>1];
    while(i<j)
    {
        do i++;
        while(a[i]<x);
        do j--;
        while(a[j]>x);
        if(i<j)
            swap(a[i],a[j]);
    }
    int p=j-l+1;
    if(p>=k)findk(a,l,j,k);
    else findk(a,j+1,r,k-p);
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        int c;scanf("%d%d",&c,&a[i]);
    }
    int res=findk(a,1,n,n/2);
    long long ans=0;
    for(int i=1;i<=n;i++)
        ans+=abs(a[i]-res);
    cout<<ans;
    return 0;
}

4.子序列的平均值

给定一个长度为n的非负序列A,请你找出一个长度不小于L的子段(子段是序列A中一些连续的元素构成的集合),使得子段中数值的平均值最大。最终输出这个最大的平均值。

输入格式:

第一行两个整数n,L(1<=L<=n<=100,000)

以下n行,每行一个非负整数,表示序列A中每个元素的值。

输出格式:

一个整数,欲求的最大平均值乘以1000后的结果(注意不要四舍五入,直接输出)

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
double a[N],s[N];
int n,l;
bool check(double mid)
{
    for(int i=1;i<=n;i++)s[i]=s[i-1]+a[i]-mid;
    double mi=s[0];
    for(int i=l;i<=n;i++)
    {
        mi=min(mi,s[i-l]);
        if(s[i]-mi>=0)
            return true;
    }
    return false;
}
int main()
{
    cin>>n>>l;
    double l=0,r=0;
    for(int i=1;i<=n;i++)cin>>a[i],r=max(r,a[i]);
    while(r-l>1e-6)
    {
        double mid=(l+r)/2;
        if(check(mid))l=mid;
        else r=mid;
    }
    printf("%d",(int)(r*1000));
}

5.第k小元素

给定一个大小为n(1≤n≤1000000)且无序的整型数组,数组中可能存在相同元素,请找出该数组第k(1≤k≤n)小的元素,注意这里的第k小元素指的是按从小到大排序后的第k个位置上的元素。

输入格式:

每个输入文件为一个测试用例,每个文件的第一行给出两个正整数n和k,第二行给出n个整数,其间以空格分隔。

输出格式:

输出第k小元素的值。

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;

int a[N];
int t,n;
int findk(int a[],int l,int r,int k)
{
    if(l>=r)
        return a[l];
    int i=l-1,j=r+1;
    int x=a[l+r>>1];
    while(i<j)
    {
        do i++;
        while(a[i]<x);
        do j--;
        while(a[j]>x);
        if(i<j)
            swap(a[i],a[j]);
    }
    int p=j-l+1;
    if(p>=k)findk(a,l,j,k);
    else findk(a,j+1,r,k-p);
}
int main()
{
    int k;cin>>n>>k;
    for(int i=1;i<=n;i++)cin>>a[i];
    cout<<findk(a,1,n,k)<<endl;
    return 0;
}

6.铺设油井管道

某石油公司有n口油井,为方便输送石油,计划修建输油管道。根据设计要求,水平方向有一条主管道,每口油井修一条垂直方向的支线管道通向主管道。请设计一种算法确定主管道的位置,使得所有油井到主管道之间的支线管道长度的总和最小。提示:复杂度为O(n)才能通过所有测试用例。

pipe.png

输入格式:

每个输入文件为一个测试用例,每个文件的第一行给出一个正整数n(1≤n≤1000000),表示油井数量,从第二行起的n行数据,表示每口油井的位置,每行包含以空格分隔的两个整数,分别表示每口油井的横坐标x(−10000≤x≤10000)和纵坐标y(−10000≤y≤10000)。

输出格式:

输出各油井到主管道之间的支管道最小长度总和。

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int a[N];
int main()
{
    int n;cin>>n;
    for(int i=0;i<=n;i++)
    {int c;scanf("%d%d",&c,&a[i]);}
    sort(a,a+n);
    int res=a[n/2];
    long long ans=0;
    for(int i=0;i<n;i++)
        ans+=abs(a[i]-res);
    cout<<ans;
}

 7.第k小

有n个数,求第k小的数。例如在数:{1 3 5 7 9 8 4 2 6 10}中,第3小的数是3 。

输入格式:

第一行输入两个数,n和k(n<=1000000),分别表示总的n个数和求第k小的数。第二行输入n个数( 最大数<10^7)。

输出格式:

一个数,表示第k小的数。

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;

int a[N];
int t,n;
int findk(int a[],int l,int r,int k)
{
    if(l==r)
        return a[l];
    int i=l-1,j=r+1;
    int x=a[l+r>>1];
    while(i<j)
    {
        do i++;
        while(a[i]<x);
        do j--;
        while(a[j]>x);
        if(i<j)
            swap(a[i],a[j]);
    }
    int p=j-l+1;
    if(p>=k)findk(a,l,j,k);
    else findk(a,j+1,r,k-p);
}
int main()
{
    int k;cin>>n>>k;
    for(int i=1;i<=n;i++)cin>>a[i];
    cout<<findk(a,1,n,k)<<endl;
    return 0;
}

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值