2017级算法模拟上机准备篇(归并排序)

归并排序是分治法的一个最经典也是最基础的应用

Divide And Conquer的思想很重要

归并排序的的Divide采用了简单的二分 Conquer采用的是将两个有序数组合并为一个有序数组。

2014-Inverse number:Reborn 逆序数求解

#include <algorithm>
#include <iostream>
#include <cstring>
using namespace std;
const int maxlen=1e6 + 10;
long long ans;
int ar[maxlen];
void Merge(int begin,int end){
    int temp[maxlen];
    int i,j,k,p,q;
    int middle=(begin+end)/2;
    p=begin;
    q=middle+1;
    k=begin;
    while(p<=middle && q<=end){
        if(ar[p]<=ar[q]) temp[k++]=ar[p++];
        else {
            temp[k++]=ar[q++];
            ans+=middle-p+1;
        }
    }
    while(p<=middle) temp[k++]=ar[p++];
    while(q<=end) temp[k++]=ar[q++];
    for(k=begin;k<=end;k++)
        ar[k]=temp[k];
}
void MergeSort(int begin,int end){
    int i,j;
    if(begin < end){
        int middle=(begin + end)/2;
        MergeSort(begin,middle);
        MergeSort(middle+1,end);
        Merge(begin,end);
    }
}
int main(){
    int n,i,j,k;
    while(~scanf("%d",&n)){
         for(i=0;i<n;i++)
            scanf("%d",&ar[i]);
          ans=0;
          MergeSort(0,n-1);
        printf("%lld\n",ans);
    }
    return 0;
}

利用归并排序来求解逆序数最巧妙的地方是在原本的合并两个有序数组的过程中,借用了有序的思想。

如果将一个数组分为两部分,那么逆序数只可能在左右两部分或者横跨两部分中出现,显然在子数组都有序的情况下。

逆序数只会出现在横跨两部分之间,那么其实在合并两个子数组的过程中,就可以顺便统计

核心代码:

if(ar[p]<=ar[q]) temp[k++]=ar[p++];
        else {
            temp[k++]=ar[q++];
            ans+=middle-p+1;
        }

2015-模式寻对 几乎是归并排序的翻版

但值得注意的是在归并排序的过程中,其实也在修改数组的内容,记得要复原,或者使用其他方法来保存。

2016-D&C--玲珑数 (逆序数的进阶版)

这道题在归并排序的基础上做了很大的改变

关键点是限制了条件后,无法使用排序过程的trick,只能在归并排序的基础上,利用有序性移动指针来减少部分时间损耗。

#include <algorithm>
#include <iostream>
#include <cstring>
using namespace std;
const int maxlen=1e6 + 10;
long long ans;
long long ar[maxlen];
long long br[maxlen];
void Merge(long long begin,long long end){
    long long temp[maxlen];
    long long i,j,k,p,q;
    long long middle=(begin+end)/2;
    p=begin;
    q=middle+1;
    k=begin;
    while(p<=middle && q<=end){
        if(ar[p]<=ar[q]) temp[k++]=ar[p++];
        else temp[k++]=ar[q++];
    }
    while(p<=middle) temp[k++]=ar[p++];
    while(q<=end) temp[k++]=ar[q++];
    for(k=begin;k<=end;k++)
        ar[k]=temp[k];
}
void MergeSort(long long begin,long long end){
    int i,j;
    if(begin < end){
        int middle=(begin + end)/2;
        MergeSort(begin,middle);
        MergeSort(middle+1,end);
        j=middle+1;
        for(i=begin;i<=middle;i++){
            while(j<=end && ar[i]>2*ar[j])
                j++;
            ans+=j-(middle+1);
        }
        Merge(begin,end);
    }
}
int main(){
    long long n,i,j,k,t,x,y;
    while(~scanf("%lld",&n)){
         for(i=0;i<n;i++)
            scanf("%lld",&br[i]);
            scanf("%lld",&t);
        while(t--){
            ans=0;
            scanf("%lld %lld",&x,&y);
            if(x>y) swap(x,y);
            for(i=x;i<=y;i++)
                ar[i]=br[i];
          MergeSort(x,y);
        printf("%lld\n",ans);
        }
    }
    return 0;
}

关键代码是利用有序性的双指针移动遍历法。0(n)

j=middle+1;

for(i=begin;i<=middle;i++){

while(j<=end && ar[i]>2*ar[j])

j++;

ans+=j-(middle+1);

}

还有两个小的坑点吧,一是给定的区间是p和q,但是前后次序却没有给定,必要时要通过swap函数来调整。

还有就是一个老生常谈的问题2*int 可能会爆int 解决办法就是能long long 绝不int

2017-序列优美差值 (再度进阶版)

给定一个序列a,询问满足i<ji<j且 L≤a[j]?a[i]≤RL≤a[j]?a[i]≤R的点对(i,j)(i,j)数量

#include <algorithm>
#include <iostream>
#include <cstring>
using namespace std;
const int maxlen=1e6 + 10;
long long ans;
long long ar[maxlen];
long long br[maxlen];
long long L,R;
void Merge(long long begin,long long end){
    long long temp[maxlen];
    long long i,j,k,p,q;
    long long middle=(begin+end)/2;
    p=begin;
    q=middle+1;
    k=begin;
    while(p<=middle && q<=end){
        if(ar[p]<=ar[q]) temp[k++]=ar[p++];
        else temp[k++]=ar[q++];
    }
    while(p<=middle) temp[k++]=ar[p++];
    while(q<=end) temp[k++]=ar[q++];
    for(k=begin;k<=end;k++)
        ar[k]=temp[k];
}
void MergeSort(long long begin,long long end){
    int i,j;
    if(begin < end){
        int middle=(begin + end)/2;
        MergeSort(begin,middle);
        MergeSort(middle+1,end);

        j=middle+1;
        int st,ed;

        st=ed=begin;
        for(i=middle+1;i<=end;i++){
            while(ar[i]-ar[ed] >= L && ed<=middle)
                ed++;
            while(ar[i]-ar[st] > R && st<=middle)
                st++;
            ans+=ed-st;
        }
        Merge(begin,end);
    }
}
int main(){
    long long n,i,j,k,t,x,y,T;
    scanf("%lld\n",&T);
    while(T--){
        scanf("%lld %lld %lld",&n,&L,&R);
         for(i=0;i<n;i++)
            scanf("%lld",&ar[i]);
            ans=0;
          MergeSort(0,n-1);
          printf("%lld\n",ans);
    }
    return 0;
}

这个问题的求解十分的巧妙 利用归并排序来求解问题 就要想办法利用有序性,本题是双指针移动,第一步求解其实已经利用一个数组的单调性,st和ed的单向移动利用了另一个数组的单调性,非常巧妙。

2017—数组优美和值(前缀和 + 分治排序)

原文地址:https://www.cnblogs.com/visper/p/10111420.html

时间: 2024-09-30 02:14:55

2017级算法模拟上机准备篇(归并排序)的相关文章

2017级算法模拟上机准备篇(一)

回顾一下往年的考题知识点: The Last Battle!! 2014级算法期末上机 简单题--I wanna be the 升级者Ⅰ 结构体排序  STL:sort() 简单题--I wanna be the 升级者Ⅱ 二分 :STL : lower_bound () 简单题--Railway Station 数据结构 : 栈操作模拟 简单题--我在东北玩泥巴:博弈论 中等题--零崎的考试:DP 中等题--伪流水线调度:费用流 网络流 中等题--果园连连看:BFS 图论 难题--线段和直线:

2017级算法模拟上机准备篇(序列DP 进阶_1)

进阶版的序列DP 从一道题的优化开始 ModricWang的序列问题 题目描述:给定一个序列,求出这个序列中的最长上升子序列的长度. 这道题的本质还是求解一个最长上升子序列的问题 相对与之前提到过的O(n^2)的算法 我们可以重新整理思路 用O(nlogn)的思路来写,用贪心和二分优化之前的算法 我们设置新的DP数组//dp[i]代表的是当前长度为i的上升子序列的末尾元素的大小 状态转移方程为如果dp[len] < ar[i] 那么就将数ar[i]加到dp数组尾部. 反之,说明可以继续优化,显然

2017级算法第三次上机-B.SkyLee逛漫展

ALS 一道动态规划最经典的题目 动态规划实质上其实就是表格法,利用表格来记录每个子问题的解. DP所关注的其实是递归 即一个较小问题的解和一个较大问题的状态转移问题. 其次还要关注的其实还是是初始值的设立,这个决定了后续的递推能否顺利的进行. 还有要思考好dp数组所代表的具体的含义 这样在状态转移的过程中 也可以好一点理解. #include <iostream> #include <algorithm> #include <cstring> using namesp

2017级算法第二次上机-C.芸如的入学测试

这道题考察的实际就是一个简单的前缀和问题.值的注意的还是取模的问题.两个数取模,以防万一,出现对负数取模的问题 可以先加上mod然后再对mod取模. 能long long就不int #include <algorithm> #include <iostream> using namespace std; const int maxlen = 1e6 + 10; const int mod = 10007; long long ar[maxlen]; long long prefix

2017级算法第二次上机-E.SkyLee的图书整理

这道题考察了map函数的使用,map最简单的理解就是把一个数映射到一个数.然后使用map也类似hash数组的方法即可 map的头文件是<map> map<int,int> 将一个数映射到一个数 #include <algorithm> #include <iostream> #include <map> using namespace std; int main(){ int n,t,x,i,j,k; while(~scanf("%d

P1-2017级算法第一次上机 A 水水的斐波那契数列

题目描述 相信大家都学过斐波那契数列,虽然很简单,但是斐波那契数列却是很重要的哦,那么让我们来复习一下斐波那契数列吧! 输入 多组数据输入 每行一个整数n (0<n<=30) 输出 对于每组数据,输出一行,为斐波那契数列第n 项的值 输入样例 1 2 3 4 输出样例 1 1 2 3 思路 用一个一维数组f(n)来表示斐波那契数列第n项的值. 则根据定义,后一项等于前两项之和,很容易得到:f(n) = f(n-1) + f(n-2). 有两种做法,递归和循环. 虽然很多人都说递归好理解,但是作

P1-2017级算法第一次上机 G SkyLee在GameStop

题目描述 SkyLee有一天逛街的时候看到一家新开业的GameStop,里面卖各种各样的游戏. 商店里所有的游戏都按游戏名的字典序从小到大排列好了,小的在里面,大的在外面. SkyLee想要把所有的游戏都试玩(买不起游戏只能看看),但是有些问题: 1.游戏只能从展示架的一侧拿出来 2.SkyLee只能拿1个游戏试玩 3.为了不被商店老板发现蹊跷,SkyLee把游戏光盘放回去的时候总要保证每个展示架的游戏仍然按照字典序从小到大排列(小的在里面,大的在外面) 4.SkyLee虽然没钱但是不可能偷游戏

A1-2017级算法第一次上机练习赛 C AlvinZH去图书馆

题目描述 AlvinZH最近在看<冰与火之歌>系列,这天,他又看完了一本书,于是决定去图书馆再借一本.大家知道,在去图书馆的路上,有一条"扯蛋路".大概是这个样子的(秀一波拍照技术): AlvinZH从第一块石砖出发,接下来他可以走到第二块石砖或第三块石砖,有时候走的很不爽,甚至可以直接跨过两个石砖,到达第四块石砖,但是不能连续两次这种操作,因为这样...对身体不好.现在假设有一条含n块石砖的小路,请你计算出AlvinZH从第一块石砖出发有多少种安全的走法. 输入 输入将由

A1-2017级算法上机第一次练习赛 P ModricWang&#39;s Number Theory II

题目描述 ModricWang has found a list containing n numbers. He calls a list bad if and only if it is not empty and gcd (see notes section for more information) of numbers in the list is 1. ModricWang can perform two types of operations: Choose a number an