文章包含四部分
归并排序主要分为两大步:
1.分解
2.合并
关于归并排序的具体原理请自行百度.
这里只略讲一下归并排序的代码实现方式
我们采用二分递归的方式处理
我们采用三个指针来实现
板子题:快速排序
Code:
#include<iostream>
#include<cstdio>
using namespace std;
const int N=100000+5;
int n;
int a[N];//需要排序的数组
int r[N];//用来辅助排序的数组
void msort(int s,int t)
{
if(s==t) return;//如果只有一个数就返回
int mid=(s+t)/2;
//递归分解
msort(s,mid);//分解左边
msort(mid+1,t);//分解右边
int i=s,j=mid+1,k=s;
//合并左右序列
while(i<=mid && j<=t) //正常排序合并
{
if(a[i]<=a[j]) {r[k]=a[i];k++;i++;}
else {r[k]=a[j];k++;j++;}
}
while(i<=mid){r[k]=a[i];k++;i++;}//复制排序后左边子序列剩余
while(j<=t){r[k]=a[j];k++;j++;}//复制排序后右边子序列剩余
for(int i=s;i<=t;i++)
a[i]=r[i];//更新a数组
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
msort(1,n);
for(int i=1;i<=n;i++)
printf("%d ",a[i]);
return 0;
}
测试结果:
第一个是
S
T
L
STL
STL里的
s
o
r
t
sort
sort
第二个是开了
O
2
O2
O2的
s
o
r
t
sort
sort
第三个是手写的归并排序
n o i p noip noip不能开 O 2 O2 O2
Q w Q QwQ QwQ
模板题
关于逆序的定义:大的数排在小的数前面
当然你也可以用冒泡排序
实现方法:
每次合并的时候,记录有多少次交换
假定我们要将一串无序数字排成升序(从小到大)
根据归并排序的原理,每次我们进行合并操作的时候,左边子序列中小的数都会被合并进辅助数组。
这时如果左边子序列有数剩余,则说明这些数都比右边子序列的数要大,那么我们可以根据这一点来计算逆序对的个数
核心Code:
while(i<=mid && j<=t)
{
if(a[i]<=a[j]) {r[k]=a[i];k++;i++;}
else {r[k]=a[j];k++;j++;ans+=mid-i+1;}//核心操作
//mid-i+1即左边子序列剩余元素的个数
}
其余部分没有什么差别
模板题Code:
#include<iostream>
#include<cstdio>
using namespace std;
const int N=5e5+5;
long long n,ans;
int a[N];
int r[N];
void msort(int s,int t)
{
if(s==t) return;
int mid=(s+t)/2;
msort(s,mid);
msort(mid+1,t);
int i=s,j=mid+1,k=s;
while(i<=mid && j<=t)
{
if(a[i]<=a[j]) {r[k]=a[i];k++;i++;}
else {r[k]=a[j];k++;j++;ans+=mid-i+1;}//核心操作
//mid-i+1即左边子序列剩余元素的个数
}
while(i<=mid){r[k]=a[i];k++;i++;}
while(j<=t){r[k]=a[j];k++;j++;}
for(int i=s;i<=t;i++)
a[i]=r[i];
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
msort(1,n);
printf("%lld",ans);
return 0;
}
最接近神的人
关键词:
交换序列中相邻的两个元素
用最少的交换次数使原序列变成不下降序列
显然在求逆序对的个数 Q w Q QwQ QwQ
套上板子即可
瑞士轮
这里只用到了归并排序中的合并操作
分析:
每组比赛的胜者:赛前,总分是按降序排的;获胜后都得1分,仍是降序;
每组比赛的负者:赛前,总分是按降序排的;不得分,仍是降序。
先按初始分数排序,然后按分数高低两人一组比赛;
胜者入队 A A A,负者入队 B B B。这样 A A A、 B B B自身仍是有序的;
只需进行合并操作即可,合并操作的复杂度是 O ( n ) O(n) O(n),而如果用快排其复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)
此处引用List大佬的博客
Code:
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=1e5+5;
int n,r,q;
struct fengzi
{
int id,sorce,power;
}m[2*N],x[2*N],y[2*N];
//m是选手,x是胜利者,y是失败者
bool cmp(fengzi a,fengzi b)
{
if(a.sorce>b.sorce) return 1;//依题意从高到低排序
if(a.sorce==b.sorce && a.id<b.id) return 1;//处理特殊情况
return 0;
}
//合并操作
void merge()
{
int i=1,j=1,cnt=0;
while(cnt<=2*n)
{
if(i>n)
{
while(j<=n){m[++cnt]=y[j];j++;}
break;
}
if(j>n)
{
while(i<=n){m[++cnt]=y[i];i++;}
break;
}
if(x[i].sorce>y[j].sorce || (x[i].sorce==y[j].sorce && x[i].id<y[j].id))//依题意更新排名
m[++cnt]=x[i++];
else m[++cnt]=y[j++];
}
}
int main()
{
scanf("%d%d%d",&n,&r,&q);
for(int i=1;i<=2*n;i++)
{
scanf("%d",&m[i].sorce);//得分
m[i].id=i;//编号
}
for(int i=1;i<=2*n;i++)
scanf("%d",&m[i].power);//实力值
sort(m+1,m+1+2*n,cmp);//排序
for(int qwq=1;qwq<=r;qwq++)//枚举每场比赛
{
int i=0,j=0;
for(int ppp=1;ppp<=n;ppp++)//枚举每组选手
{
int pos=2*ppp;
if(m[pos-1].power>m[pos].power)//如果该组中的第一个选手比第二个选手强
{
m[pos-1].sorce++;//第一个选手的分数+1
x[++i]=m[pos-1];//第一个选手分到胜利者的队伍
y[++j]=m[pos];//第二个选手分到失败者的队伍
}
else
{
m[pos].sorce++;
x[++i]=m[pos];
y[++j]=m[pos-1];
}
}
merge();//合并胜利者和失败者,更新 新的排名
}
printf("%d",m[q].id);
return 0;
}
火柴排队
分析:
由数学知识可知,要使$ \sum (a_i-b_i)^2 最 小 我 们 就 要 使 每 一 个 最小 我们就要使每一个 最小我们就要使每一个a_i-b_i$最小
也就是说, a a a中第 k k k小的数要和 b b b中第 k k k小的数排在一个位置
依据这个思想,我们可以新建一个数组
x
x
x
这个数组的下标是
a
a
a数组中第
k
k
k小的数在原序列中的编号
这个数组中存的是
b
b
b数组中第
k
k
k小的数在原序列中的编号
例如:
a
a
a的原始序列为
5
,
3
,
1
,
2
,
6
5,3,1,2,6
5,3,1,2,6
b
b
b的原始序列为
7
,
2
,
9
,
3
,
1
7,2,9,3,1
7,2,9,3,1
把两个序列从小到大排序后,
a
a
a序列为
1
,
2
,
3
,
5
,
6
1,2,3,5,6
1,2,3,5,6
b
b
b序列为
1
,
2
,
3
,
7
,
9
1,2,3,7,9
1,2,3,7,9
我们假定此时的
k
k
k为3
a
a
a序列中第
3
(
k
)
3(k)
3(k)小的数的原始编号就是
2
2
2
b
b
b序列中第
3
(
k
)
3(k)
3(k)小的数的原始编号就是
4
4
4
那 x [ 3 ] = 4 x[3]=4 x[3]=4
显然,这个数组的特性就是,如果 x [ i ] = i x[i]=i x[i]=i那么此时, a a a序列中第k小的数就和 b b b序列中的第 k k k小的数,一一对应了,即原始序列时,就有 a i − b i a_i-b_i ai−bi最小。
然后,我们的目标是,将 x x x数组升序排列,求出交换的次数,这不就转换成求逆序对了…
Code:
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int mod=99999997;
const int N=100000+5;
int n,ans,cnt;
struct team
{
int id,hight;//id是原始编号,hight是高度
}a[N],b[N];
int x[N],y[N];
//x待排序的数组
//y是辅助数组
bool cmp(team a,team b)
{
return a.hight<b.hight;//先按高度排序
}
void merge(int l,int r)
{
if(l==r) return;
int mid=(l+r)/2;
merge(l,mid);
merge(mid+1,r);
int i=l,j=mid+1,k=l;
while(i<=mid && j<=r)
{
if(x[i]>x[j])
{
y[k++]=x[j++];
ans+=mid-i+1;
ans%=mod;
}
else
y[k++]=x[i++];
}
while(i<=mid) y[k++]=x[i++];
while(j<=r) y[k++]=x[j++];
for(int i=l;i<=r;i++)
x[i]=y[i];
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i].hight);
a[i].id=i;
}
for(int i=1;i<=n;i++)
{
scanf("%d",&b[i].hight);
b[i].id=i;
}
sort(a+1,a+1+n,cmp);
sort(b+1,b+1+n,cmp);
for(int i=1;i<=n;i++)
x[a[i].id]=b[i].id;//初始化x数组
merge(1,n);//求逆序对
printf("%d",ans);
return 0;
}
下面是来自 P O J POJ POJ的水题
板子题.1–poj2299
就不放代码啦,给大家一个练手的机会,记得开 l o n g l o n g long long longlong
板子题.2–poj1804
P O J POJ POJ板子真多,题面还都不一样…,记得输出的时候要换两次行
M × N Puzzle–poj2893
这题就有点难度啦
首先我们把它给出的 M × N M\times N M×N个数转为一串数字,存进一个数组中,称为原始状态
把最终有序的状态也转为一串数字也存进一个数组,称为最终状态,
分析:
八数码问题的有解无解的结论:
一个状态表示成一维的形式,求出除0之外所有数字的逆序数之和,也就是每个数字前面比它大的数字的个数的和,称为这个状态的逆序。
若两个状态的逆序奇偶性相同,则可相互到达,否则不可相互到达。
比如:
原始状态:
1
,
3
,
0
,
2
1,3,0,2
1,3,0,2
把
0
0
0往左移动一格,变为
1
,
0
,
3
,
2
1,0,3,2
1,0,3,2
逆序对的个数是没有发生改变的
注意我们算逆序对的个数时,不把 0 0 0考虑进去
- 若列数为奇数,上下移动不会是逆序对的奇偶性发生改变,这个自己画画图就能理解了.
- 若列数为偶数,我们就要考虑 0 0 0移动的距离了, 0 0 0上下移动一格,就会增加或减少奇数个逆序对
现在考虑 M × N M\times N M×N的棋盘
首先算出逆序对的个数,在处理出 0 0 0要移动的距离,然后让看看两状态奇偶性是否一致即可。
Code:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n,m,cnt,ans,step;
int a[1000*1000];
int b[1000*1000];
//求逆序对的个数
void merge(int l,int r)
{
if(l==r) return;
int mid=(l+r)/2;
merge(l,mid);
merge(mid+1,r);
int i=l,j=mid+1,k=l;
while(i<=mid && j<=r)
{
if(a[i]>a[j])
{
b[k++]=a[j++];
ans+=mid-i+1;
}
else b[k++]=a[i++];
}
while(i<=mid) b[k++]=a[i++];
while(j<=r) b[k++]=a[j++];
for(int i=l;i<=r;i++)
a[i]=b[i];
}
int main()
{
while(scanf("%d%d",&n,&m))
{
if(n==0) break;
memset(a,0,sizeof(a));
// memset(b,0,sizeof(b));这个不要初始化,会T掉
ans=cnt=step=0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
int x;
scanf("%d",&x);
if(x!=0) a[++cnt]=x;
else step=n-i;//计算0的移动距离
}
}
merge(1,cnt);
// cout<<ans<<endl;
if(m&1)
{
if(ans%2==0) puts("YES");//大写!!!
else puts("NO");
}
else
{
if(ans%2==step%2) puts("YES");
else puts("NO");
}
}
return 0;
}