第二节 基础算法 - 2

So_noSlack 的博客 / 2023-08-02 / 原文

例题

逆序对

题目描述

猫猫 TOM 和小老鼠 JERRY 最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。

最近,TOM 老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中 \(a_i>a_j\)\(i<j\) 的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。注意序列中可能有重复数字。

Update:数据已加强。

输入格式

第一行,一个数 \(n\),表示序列中有 \(n\)个数。

第二行 \(n\) 个数,表示给定的序列。序列中每个数字不超过 \(10^9\)

输出格式

输出序列中逆序对的数目。

样例输入 #1

6
5 4 2 6 3 1

样例输出 #1

11

提示

对于 \(25\%\) 的数据,\(n \leq 2500\)

对于 \(50\%\) 的数据,\(n \leq 4 \times 10^4\)

对于所有数据,\(n \leq 5 \times 10^5\)

请使用较快的输入输出

应该不会 \(O(n^2)\) 过 50 万吧 by chen_zhe

点击查看代码
#include<iostream>
#include<cstdio>
using namespace std;
#define MAXN 524288
int a[MAXN],tmp[MAXN],n;
long long merge(int a[],int l,int m,int r){
	long long step = 0;
	int i = l,j = m+1;
	for(int k = l;k <= r;k ++){
		if((j > r) || (i <= m) && (a[i] <= a[j])) tmp[k] = a[i ++];
		else tmp[k] = a[j ++], step += m-i+1;
	}
	for(int i = l;i <= r;i ++) a[i] = tmp[i];
	return step;
}

long long msort(int a[],int l,int r){
	if(l >= r) return 0;
	int mid = l+(r-l)/2;
	return msort(a,l,mid)+msort(a,mid+1,r)+merge(a,l,mid,r);
}

int main(){
	scanf("%d",&n);
	for(int i = 1;i <= n;i ++) scanf("%d",&a[i]);
	printf("%lld",msort(a,1,n));
	return 0;
}

[NOIP2015 提高组] 跳石头

题目背景

一年一度的“跳石头”比赛又要开始了!

题目描述

这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有 \(N\) 块岩石(不含起点和终点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。

为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走 \(M\) 块岩石(不能移走起点和终点的岩石)。

输入格式

第一行包含三个整数 \(L,N,M\),分别表示起点到终点的距离,起点和终点之间的岩石数,以及组委会至多移走的岩石数。保证 \(L \geq 1\)\(N \geq M \geq 0\)

接下来 \(N\) 行,每行一个整数,第 \(i\) 行的整数 \(D_i( 0 < D_i < L)\), 表示第 \(i\) 块岩石与起点的距离。这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同一个位置。

输出格式

一个整数,即最短跳跃距离的最大值。

样例输入 #1

25 5 2 
2
11
14
17 
21

样例输出 #1

4

提示

输入输出样例 1 说明

将与起点距离为 \(2\)\(14\) 的两个岩石移走后,最短的跳跃距离为 \(4\)(从与起点距离 \(17\) 的岩石跳到距离 \(21\) 的岩石,或者从距离 \(21\) 的岩石跳到终点)。

数据规模与约定

对于 \(20\%\)的数据,\(0 \le M \le N \le 10\)
对于 \(50\%\) 的数据,\(0 \le M \le N \le 100\)
对于 \(100\%\)的数据,\(0 \le M \le N \le 50000,1 \le L \le 10^9\)

点击查看代码
#include<bits/stdc++.h>
#include<cstdio>
using namespace std;
inline int read(){
  int x=0,w=1 ; char ch=0;
  while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
  while(ch>='0'&&ch<='9'){x=x*10+(ch-'0');ch=getchar();}
  return x*w;
}
int l,n,m,a[100005],c,mid,ans;
bool pick_stone(int x){
	int last_p = 0,num = 0;
	for(int i=1;i<=n;i++){
		if(last_p+x > a[i])num++;
		else last_p = a[i];
	}
	if(num>m) return false;
	return true;
}
int main(){
	l=read();n=read();m=read();
	for(int i=1;i<=n;i++)a[i]=read();
	int p=1,r=l;
	while(p<=r){
		mid = p+(r-p)/2;
		if(pick_stone(mid))ans=mid,p=mid+1;
		else r=mid-1;
	}
	cout<<ans;
	return 0;
}

[NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G

题目描述

在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。

每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过 \(n-1\) 次合并之后, 就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。

因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为 \(1\) ,并且已知果子的种类 数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。

例如有 \(3\) 种果子,数目依次为 \(1\)\(2\)\(9\) 。可以先将 \(1\)\(2\) 堆合并,新堆数目为 \(3\) ,耗费体力为 \(3\) 。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为 \(12\) ,耗费体力为 \(12\) 。所以多多总共耗费体力 \(=3+12=15\) 。可以证明 \(15\) 为最小的体力耗费值。

输入格式

共两行。
第一行是一个整数 \(n(1\leq n\leq 10000)\) ,表示果子的种类数。

第二行包含 \(n\) 个整数,用空格分隔,第 \(i\) 个整数 \(a_i(1\leq a_i\leq 20000)\) 是第 \(i\) 种果子的数目。

输出格式

一个整数,也就是最小的体力耗费值。输入数据保证这个值小于 \(2^{31}\)

样例输入 #1

3 
1 2 9

样例输出 #1

15

提示

对于 \(30\%\) 的数据,保证有 \(n \le 1000\)

对于 \(50\%\) 的数据,保证有 \(n \le 5000\)

对于全部的数据,保证有 \(n \le 10000\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n, ans = 0;
priority_queue<int, vector<int>, greater<int> > q;
int main() {
	scanf("%d", &n);
	for(int i = 1; i <= n; i ++) {
		int x; 
		scanf("%d", &x);
		q.push(x);
	}
	while(q.size() > 1) {
		int x = q.top(); q.pop();
		int y = q.top(); q.pop();
		ans += x + y;
		q.push(x + y);
	}
	printf("%d\n", ans);
	return 0;
}