#归并排序 归并排序的刷题记录 ~20.09.25

归并排序 大致模板:

void merge(ll l, ll r){
	if(l == r) return ;
	//因为只有mid和mid + 1,那么只有(mid)、(mid + 1)会触碰边界l、r;
	//那么我们要考虑的边界情况就只有l == r
	ll mid = (l + r) >> 1;
	merge(l, mid);
	merge(mid + 1, r);
	ll i = l, j = mid + 1, cnt = l;
	
	while(i <= mid && j <= r)
		//这个循环是为了将前后两部分按大小排好序
		//由于前半部分和后半部分都是已经按照一定顺序排好了的单调队列,那么每次只需将两个队列的头指针进行比较就可以找到当前两个队列所有数中的最小值了
		//这也是为什么要分成前半部分和后半部分进行操作的原因
		if(a[i] <= a[j]) b[cnt ++] = a[i ++];
		else b[cnt ++] = a[j ++];
	while(i <= mid) b[cnt ++] = a[i ++];
	while(j <= r) b[cnt ++] = a[j ++];
	for(ll i = l; i <= r; i ++) a[i] = b[i];
}

P1774 最接近神的人

P1774 最接近神的人

思路

归并排序

void merge(ll l, ll r){
	if(l == r) return ;
	ll mid = (l + r) >> 1;
	merge(l, mid);
	merge(mid + 1, r);
	ll i = l, j = mid + 1, cnt = l;
	
	while(i <= mid && j <= r)
		if(a[i] <= a[j]) b[cnt ++] = a[i ++];
		else{
			b[cnt ++] = a[j ++];
			ans += (mid - i + 1);//这里针对题目要求
		} 
	while(i <= mid) b[cnt ++] = a[i ++];
	while(j <= r) b[cnt ++] = a[j ++];
	for(ll i = l; i <= r; i ++) a[i] = b[i];
}
ans += (mid - i + 1);

这句话的意思是,记录所有移动的总数,而且一定是最少的:
为什么是mid - i + 1?

首先要明白,归并排序是每次将当前要处理的序列分两半,
我们将 左边从L到 mid 的指针为 i, 右边 从 mid + 1 到 R的指针叫 j;
每当我们找到一个要交换位置的 比i小 和 j 的时候,都要将 j 这个位置的数移到  i位置之前,
这其中越过的数就包括了 i , 于是要 mid - i + 1 中的 +1 ;

而且,每次我们处理前的这个序列,其左半边与右半边的数一定是从小到大排好的,
而每一次将后半部分移动到前面之后,接下来 j 的位置一定是紧贴着 mid 的,
而 j上的数的终点就是 i这个位置的前面,这就是mid - i + 1 中的 mid - i 的由来;

综合起来就是 每找到一个需要往前走的 j 上的数 ,其向前交换的步数就是  mid - i + 1;

答案

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5e5 + 10, M = 10;
ll a[N], b[N];
ll n;
ll ans = 0;

void merge(ll l, ll r){
	if(l == r) return ;
	ll mid = (l + r) >> 1;
	merge(l, mid);
	merge(mid + 1, r);
	ll i = l, j = mid + 1, cnt = l;
	
	while(i <= mid && j <= r)
		if(a[i] <= a[j]) b[cnt ++] = a[i ++];
		else{
			b[cnt ++] = a[j ++];
			ans += (mid - i + 1);
		} 
	while(i <= mid) b[cnt ++] = a[i ++];
	while(j <= r) b[cnt ++] = a[j ++];
	for(ll i = l; i <= r; i ++) a[i] = b[i];
}


int main(){
//	freopen("ttt.in", "r", stdin);
//	freopen("ttt.out", "w", stdout);
	
	scanf("%lld", &n);
//	cin >> n;
	for(ll i = 1; i <= n; i ++) 
		scanf("%lld", &a[i]);
//		cin >> a[i];
	merge(1, n);
	
	cout << ans;
	return 0;
}

 /*
             _  
 /     ∠ 
/ |     / /    _
| Z ____∠ /   / ヽ
/     ヽ   /    〉
|     ` /  /   /
 ● ` ● * 〈 /
(()   () /    \
 -` -  ィ   |   //
/  /   / /   \\
\_/  (_/ |  //
/        |//
_ / ̄ ̄`―_丿

*/

.

P1309 瑞士轮

P1309 瑞士轮

思路

这道题活用了 二分 和 归并 的思想;

比赛前的分数是已经(手动)排好了的,而且比赛的原则是每两个相邻的(1和2 , 3和4……)比赛;
也就是说,从一开始所有选手就已经分成了两两一组的情况,且保证后面的组里面的选手的分数必定大于前面所有的组里面的选手;
那么就让赢的人一组,输的人一组的;
操作完成后两组是必定从小到大排好序的;
而这道题会改变的值也只有选手进行一场比赛之后的分数,
而改变只是每两个人之间发生的,所以需要改变的顺序很少很少,用归并再适合不过了!
(如果想要每轮下来都 快排一次的劝退,因为快排的话每次二分都要改变不少的值,浪费时间)
……
分完之后直接 重新排序并替换 就可以了;

答案

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10, M = 10;
int n, R, Q;
int a[N];
int win[N], lose[N]; 
int s[N], w[N];

bool cmp(int x, int y){
	if(s[x] == s[y]) return x < y;
	return s[x] > s[y];
}
int merge(){
	int i , j;
	i = j = 1;  
	a[0] = 0;  
	while(i <= win[0] && j <= lose[0])  
		if(cmp( win[i], lose[j])) a[++ a[0]]= win[i++];  
	    else a[++ a[0]] = lose[j ++];
		  
	  while(i <= win[0] ) a[++ a[0]]= win[i ++];  
	  while(j <= lose[0] ) a[++ a[0]]= lose[j ++]; 
}

int main(){
//	freopen("ttt.in", "r", stdin);
//	freopen("ttt.out", "w", stdout);
	
	scanf("%d%d%d", &n, &R, &Q);
	n *= 2;
	for(int i = 1; i <= n; i ++) scanf("%d", &s[i]); //分数 
	for(int i = 1; i <= n; i ++) scanf("%d", &w[i]);//实力 
	for(int i = 1; i <= n; i ++) a[i] = i;//初始排序 
	
	sort(a + 1, a + n + 1, cmp);
	for(int i = 1; i <= R; i ++){
		//这里先按照胜负分好组
		win[0] = lose[0] = 0;  
	    for(int j= 1; j <= n; j += 2)  
	    
	    	if(w[ a[j]] > w[ a[j+1]]){  
	        	s[a[j]] ++;  
	        	win[++ win[0]] = a[j];  
	        	lose[++ lose[0]] = a[j + 1];  
	    	}else{  
	        	s[a[j+1]] ++;  
	            win[++ win[0]] = a[j + 1];  
	            lose[++ lose[0]] = a[j];  
	        }     
	        
		merge();
	}
	
	cout << a[Q];
	return 0;
}

 /*
             _  
 /     ∠ 
/ |     / /    _
| Z ____∠ /   / ヽ
/     ヽ   /    〉
|     ` /  /   /
 ● ` ● * 〈 /
(()   () /    \
 -` -  ィ   |   //
/  /   / /   \\
\_/  (_/ |  //
/        |//
_ / ̄ ̄`―_丿

*/










P1626 象棋比赛

P1626 象棋比赛

思路

这道题题目好绕……
总的来说就是要你先排个序,然后记录每两个相邻的数的差cha;
然后 输出 cha[i ~ k] 的总和;

P:为什么用 归并呢?
S:可以不用啊。
P:……
S:正解:
其实归并相对于快排要优秀一些

1.时间稳定(快排的复杂度下限nlogn,上限 n2 很容易被卡,而归并固定nlogn)
2.排序稳定(快排不稳定)(稳定同样大小的数排序完之后是否按读入的先后顺序输出

归并思路:分治,将原串拆半,然后将这两半排序(递归),再开一个临时数组,依次将两个数组的最小值加入这个临时数组,最后复制回原数组

答案

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10, M = 10;
int a[N], b[N];
void merge(int l, int r){
	if(l == r) return ;
	int mid = (l + r) >> 1;
	merge(l, mid);
	merge(mid + 1, r);
	int i = l, j = mid + 1, cnt = l;
	
	while(i <= mid && j <= r)
		if(a[i] < a[j]) b[cnt ++] = a[i ++];
		else b[cnt ++] = a[j ++];
	while(i <= mid) b[cnt ++] = a[i ++];
	while(j <= r) b[cnt ++] = a[j ++];
	for(int i = l; i <= r; i ++) a[i] = b[i];
}

int main(){
//	freopen("ttt.in", "r", stdin);
//	freopen("ttt.out", "w", stdout);
	int n, k;
	int ans = 0;
	scanf("%d%d", &n, &k);
	for(int i = 1; i <= n; i ++) scanf("%d", &a[i]);
	merge(1, n);
	for(int i = 1; i < n ; i ++) b[i] = a[i + 1] - a[i];//差
	for(int i = 1; i < n ; i ++) a[i] = b[i];
	merge(1, n - 1); 
	for(int i = 1; i <= k; i ++) ans += a[i];
	
	cout << ans;
	return 0;
}

dummy

原文地址:https://www.cnblogs.com/yuanyulin/p/14026744.html