IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 1010 Lehmer Code (35 分)(思路+详解+树状数组的学习+逆序对+map+vector) 超级详细 Come baby!!! -> 正文阅读

[C++知识库]1010 Lehmer Code (35 分)(思路+详解+树状数组的学习+逆序对+map+vector) 超级详细 Come baby!!!

一:题目

According to Wikipedia: “In mathematics and in particular in combinatorics, the Lehmer code is a particular way to encode each possible permutation of a sequence of n numbers.” To be more specific, for a given permutation of items {A

}, Lehmer code is a sequence of numbers {L hat L is the total number of items from Ato A which are less than A
. For example, given { 24, 35, 12, 1, 56, 23 }, the second Lehmer code L 2 is 3 since from 35 to 23 there are three items, { 12, 1, 23 }, less than the second item, 35.

Input Specification:
Each input file contains one test case. For each case, the first line gives a positive integer N (≤10
5
). Then N distinct numbers are given in the next line.

Output Specification:
For each test case, output in a line the corresponding Lehmer code. The numbers must be separated by exactly one space, and there must be no extra space at the beginning or the end of the line.

Sample Input:

6
24 35 12 1 56 23

结尾无空行
Sample Output:

3 3 1 0 1 0

二:思路:

这个题用到的是树状数组的相关知识 ,其中在树状数组的应用当中有求逆序对一项,但这个题不能直接用,这个题并没有给出 输入的数字的范围 那就无法直接用 求逆序对的方法来求,
因为会出现段错误(因为输入的数 可能大于 500000)
但是我们可以将输入的数进行转换,将他们先进行排序,将他们的下标作为他们的
代替,这样我们就控制住输入数字的范围了,这个题也已经明确指出了输入数字
不重复,所以我们可以大胆的用map,来记录他们的排序

这里举例来说明我们用排好序的下标来代替数,这样控制住数的范围,且结果一样:
在这里插入图片描述
我们可以看到 第一个数 4,4后面比其小的数 还是有 3 个;

三:上码:

 
/**
	思路:这个题并没有给出 输入的数字的范围 那就无法直接用 求逆序对的方法来求
		  因为会出现段错误(因为输入的数 可能大于 500000) 
		  但是我们可以将输入的数进行转换,将他们先进行排序,将他们的下标作为他们的
		  代替,这样我们就控制住输入数字的范围了,这个题也已经明确指出了输入数字
		  不重复,所以我们可以大胆的用map,来记录他们的排序 

*/ 
 
 
#include<bits/stdc++.h>
using namespace std;

int c[100005];

int lowbit(int x){
	return x&(-x);
}

//单点更新
void update(int x,int y,int n){
	
	while(x <= n){
		c[x] =  c[x] + y;
		x = x + lowbit(x);		
	}
	
} 

//求取前缀和
int getSum(int pos){
	int sum = 0;
	
	while(pos > 0){
		sum += c[pos];
		pos = pos - lowbit(pos);  
	}
	
	return sum;
} 

 
int main(){
	
	int a[100005];
	vector<int> v1,v2,v3;
	map<int,int>m; 
	
	int N;
	
	memset(a,0,sizeof(a));
	memset(c,0,sizeof(c));
	
	cin >> N;
	
	for(int i = 0; i < N; i++){
		
		int temp;
		cin >> temp;
		
		a[i] = temp;
		v1.push_back(temp);
	}
	
	sort(v1.begin(),v1.end()); 
	
	for(int i = 0; i < N; i++){
		m[v1[i]] = i + 1;
	}
	
	
	for(int i = N - 1; i >= 0; i--){
		
		int num = m[a[i]];		
		update(num,1,100005);
		
		//这里求的是getSum(temp) 表示的是前面比 temp小的个数其中是包含temp本身的 
		int temp = getSum(num) - 1;
		
		v2.push_back(temp);

	}
	
	 for(int i = N-1; i >= 0; i--){
	 	
	 	if(i == N - 1){
	 		cout << v2[i];
		 }else{
		 	cout << ' ' << v2[i]; 
		 }
	 	
	 }
}

在这里插入图片描述

四:介绍相关的知识

1:树状数组

(1):图示:

在这里插入图片描述

(2):相关的介绍

相关知识介绍:
1.理解图 A[i]:表示的是正常的数组
C[i]:表示的是区间的和

eg: c[1] =A[1]
c[2] = A[1] + A[2]
c[6] = A[5] + A[6]

2.那么如何表示C[i] 中 i表示的个数呢 这时候要用到lowbit(i),
lowbit(i) = i & (-i)

eg:lowbit(6) = 2
lowbit(4) = 4

3.i + lowbit[i]:表示其父亲结点的下标
eg:6 + lowbit(6) = 8

i - lowbit(i):表示其左边管辖区域的下标
eg:6 - lowbit(6) = 4

4.相关的函数
(1):求取lowbit(i)

int lowbit (int i){
return i & (-i);
}   

(2):更新单点的值,就是如果你给区间内的某个值增加一定的数,那么其父节点
也会增加相应的值
eg: A[1]比以前大了,那么C[1]也要比以前的大,他的父节点C[2],
也要跟着变大,那么的话,c[2]的父节点也要跟着变大

void update(int x,int y,int n){//参数:表示在x位置增加了y 数组长度为n

while(x <= n){
c[x] = c[x] + y;
x = x + lowbit(x);//求取父节点 
}	

}

(3):求前缀和
eg:求取前6个数的和
sum[6] = A[1] + A[2] + A[3]+ A[4]+ A[5] + A[6]

因为:C[6] = A[5] + A[6]
C[4] = A[1]+A[2]+A[3]+A[4]
那么也就是sum[6] = C[6] + C[4]

int getSum(int pos){
int sum = 0;

while(pos > 0){
sum += C[pos]

pos = pos - lowbit[pos]
}

return sum;
}

2:例题求取区间和

	题目:求出某区间每一个数的和
	输入格式
	第一行包含两个正整数n,m,分别表示该数列数字的个数和
	操作的总个数
	第二行包含n个用空格分隔的整数,其中第i个数字表示数
	列第i项的初始值
	接下来m行每行包含3个整数,表示一个操作,具体如下
	·1 x k含义:将第x个数加上k
	·2 x y含义:输出区间{x,y}内每个数的和
	输出格式
	输出包含若干行整数,即为所有操作2的结果。
	
	
	输入样例: 5 5
			   1 5 4 2 3
			   1 1 3
			   2 2 5
			   1 3 -1
			   1 4 2
			   2 1 4
			   
		输出:14
			  16
/**
	题目:求出某区间每一个数的和
	输入格式
	第一行包含两个正整数n,m,分别表示该数列数字的个数和
	操作的总个数
	第二行包含n个用空格分隔的整数,其中第i个数字表示数
	列第i项的初始值
	接下来m行每行包含3个整数,表示一个操作,具体如下
	·1 x k含义:将第x个数加上k
	·2 x y含义:输出区间{x,y}内每个数的和
	输出格式
	输出包含若干行整数,即为所有操作2的结果。
	
	
	输入样例: 5 5
			   1 5 4 2 3
			   1 1 3
			   2 2 5
			   1 3 -1
			   1 4 2
			   2 1 4
			   
		输出:14
			  16
			  
	
	相关知识介绍:
		1.理解图 A[i]:表示的是正常的数组
		         C[i]:表示的是区间的和 
		  
		    eg:  c[1] =A[1]
		  	     c[2] = A[1] + A[2]
		  	     c[6] = A[5] + A[6]
		   
		2.那么如何表示C[i] 中 i表示的个数呢 这时候要用到lowbit(i),
			lowbit(i) = i & (-i)
		
		    eg:lowbit(6) = 2
		     	lowbit(4) = 4
			
		3.i + lowbit[i]:表示其父亲结点的下标
		  eg:6 + lowbit(6) = 8
		  
		  i - lowbit(i):表示其左边管辖区域的下标
		  eg:6 - lowbit(6) = 4
		  
		4.相关的函数
			(1):求取lowbit(i)
				
				int lowbit (int i){
					return i & (-i);
				}   
		  	
		  	(2):更新单点的值,就是如果你给区间内的某个值增加一定的数,那么其父节点
			  	也会增加相应的值
				  eg: A[1]比以前大了,那么C[1]也要比以前的大,他的父节点C[2],
				  也要跟着变大,那么的话,c[2]的父节点也要跟着变大
				  
				 void update(int x,int y,int n){//参数:表示在x位置增加了y 数组长度为n
				  
					while(x <= n){
						c[x] = c[x] + y;
						x = x + lowbit(x);//求取父节点 
					}	
							
				}
				   
			(3):求前缀和
				eg:求取前6个数的和
					sum[6] = A[1] + A[2] + A[3]+ A[4]+ A[5]	+ A[6]
					
				因为:C[6] = A[5] + A[6]
					  C[4] = A[1]+A[2]+A[3]+A[4]	
				那么也就是sum[6] = C[6] + C[4]	
				
				int getSum(int pos){
					int sum = 0;
					
					while(pos > 0){
						sum += C[pos]
						
						pos = pos - lowbit[pos]
				    }
				    
				    return sum;
				}
				 	    
	
	
	

*/





#include<bits/stdc++.h>
using namespace std;

int c[1000] = {0};

int lowbit(int x){
	return x&(-x);
}

//更新单节点 
void update(int x,int y,int n){
	while(x <= n){
		c[x] = c[x] + y;
		x = x + lowbit(x);
	}
}

//求前缀和
int getSum(int pos){
	int sum = 0;
	
	while(pos > 0){
		sum += c[pos];
		pos = pos - lowbit(pos);	
	}
	
	return sum;
} 
 



int main(){
	
	int N,M;
	int a[1000];
	memset(a,0,sizeof(a));
	
	cin >> N >> M;
	
	for(int i = 1; i <= N; i++){
		
		cin >>a[i];
		//这里就是往C[i]中赋值的操作,因为初始的a[i]中的值均为0,故可以开始更新 
		update(i,a[i],N); 
	}
	
	for(int i = 0; i < M; i++){
		
		int operation,num1,num2;
		
		cin >> operation >> num1 >> num2;
		 
		if(operation == 1){
			update(num1,num2,N);
		}else if(operation == 2){
			 cout << getSum(num2) - getSum(num1-1) << endl;
		}else{
			cout << "您的输入有误!!"; 
		} 
			
	}
	
//测试数据	
//	for(int i = 1; i <= N; i++){
//		cout << c[i] << ' ';
//	} 
	
//	cout << a[99];
		
} 


/**

	·1 x k含义:将第x个数加上k
	·2 x y含义:输出区间{x,y}内每个数的和
	输出格式
	输出包含若干行整数,即为所有操作2的结果。
	
	
	输入样例: 5 5
			   1 5 4 2 3
			   1 1 3           
			   2 2 5
			   1 3 -1
			   1 4 2
			   2 1 4
			   
		输出:14
			  16
*/


//5 5
//1 5 4 2 3
//1 1 3
//2 2 5
//1 3 -1
//1 4 2
//2 1 4

3:求逆序对

何为逆序对:
什么是逆序对
设A为一个有n个数字的有序集(>1),其中所有数字各不相同。如果存在正整数i,j
使得1<=i<j<=n而且A[i]>A[j],则<A[i],A[j]>这个有序对称为A的一个逆序对,也称作逆序数。例如
数组(3 1 4 5 2)的逆序对有(3,1)(32)(42)(5,2),共4个

思路:输入的N个数 3 1 4 5 2
每次输入的值都在 A[i](i = 输入的值) 对应的位置 A[3] = 1,A[1] = 1;
其中A[] ,C[],初始化均为0,每次a[i]变化对应的更新C[i]的值
那么前面比其大的数的个数 = i - (前面比其小的个数)
= i - getSum(a[i])


/**
	何为逆序对:
	什么是逆序对
	设A为一个有n个数字的有序集(>1),其中所有数字各不相同。如果存在正整数i,j
	使得1<=i<j<=n而且A[i]>A[j],则<A[i],A[j]>这个有序对称为A的一个逆序对,也称作逆序数。例如
	数组(3 1 4 5 2)的逆序对有(3,1)(32)(42)(5,2),共4个 

	思路:输入的N个数 3 1 4 5 2
	     每次输入的值都在 A[i](i = 输入的值) 对应的位置  A[3] = 1,A[1] = 1;
		 其中A[] ,C[],初始化均为0,每次a[i]变化对应的更新C[i]的值
		 
		 那么前面比其大的数的个数 = i - (前面比其小的个数)
								  =	i - getSum(a[i]) 
	
*/ 


#include<bits/stdc++.h>
using namespace std;

int c[1000];

int lowbit(int x){
	return x&(-x);
}

//单点更新
void update(int x,int y,int n){
	
	while(x <= n){
		c[x] =  c[x] + y;
		x = x + lowbit(x);		
	}
	
} 

//求取前缀和
int getSum(int pos){
	int sum = 0;
	
	while(pos > 0){
		sum += c[pos];
		pos = pos - lowbit(pos);  
	}
	
	return sum;
} 

 
int main(){
	
	int a[1000],b[1000];
	int N;
	
	memset(a,0,sizeof(a));
	memset(b,0,sizeof(b));
	memset(c,0,sizeof(c));
	
	cin >> N;
	
	for(int i = 1; i <= N; i++){
		
		int temp;
		cin >> temp;
		
		a[temp] = 1;
		update(temp,a[temp],1000);
		
		//这里求的是getSum(temp) 表示的是前面比 temp 小的个数 
		
		cout << i - getSum(temp) << ' ';	
		
	}
	
}

4:对map和vector容器 不熟练的兄弟们可以看下这个连接

map的基本用法
vector的基本用法

五:学习记录

这个题做了3个晚上,从刚拿到直接做,到后来 开始学习树状数组,求区间和,逆序对,慢慢组成了这道题的题解

第一次做的代码:过去俩点


#include<bits/stdc++.h>
using namespace std;

int main(){
	
	int N;
	vector<int> v1,v2;
	
	cin >> N;
	
	for(int i = 0; i < N; i++){
		int temp;
		cin >> temp;	
		v1.push_back(temp);
	}
	
	for(int i = 0; i < N; i++){
		
		int count = 0;
		
		for(int j = i+1; j < N; j++){
			
			if(v1[i] > v1[j]){
				count++;
			}
		}
		
		v2.push_back(count);
		
	}
	
	int flag = 0;
	
	for(int i = 0; i < N; i++){
		
		if(flag == 0){
			cout << v2[i];
		}else{
			cout << ' ' << v2[i];
		}
		
		flag = 1;
	}
		
} 

/**
6
24 35 12 1 56 23

3 3 1 0 1 0


*/

第二次做的代码,过去4个点但是其还是有一个点超时


#include<bits/stdc++.h>
using namespace std;

int c[1000005];

int lowbit(int x){
	return x&(-x);
}

//单点更新
void update(int x,int y,int n){
	
	while(x <= n){
		c[x] =  c[x] + y;
		x = x + lowbit(x);		
	}
	
} 

//求取前缀和
int getSum(int pos){
	int sum = 0;
	
	while(pos > 0){
		sum += c[pos];
		pos = pos - lowbit(pos);  
	}
	
	return sum;
} 

 
int main(){
	
	int a[1000005],b[10000];
	vector<int> v1,v2;
	int N;
	
	int aa = 1;
	v1.push_back(aa);
	v2.push_back(aa);
	
	memset(a,0,sizeof(a));
	memset(c,0,sizeof(c));
	
	cin >> N;
	
	for(int i = 1; i <= N; i++){
		
		int temp;
		cin >> temp;
	
		v1.push_back(temp);
	}
	
	for(int i = N; i >= 1; i--){
		
		int num = v1[i];
		
		a[num] = 1;
		update(num,a[num],1000005);
		
		//这里求的是getSum(temp) 表示的是前面比 temp小的个数其中是包含temp本身的 
		int temp = getSum(num) - 1;
		
		v2.push_back(temp);

	}
	
	 for(int i = N; i >= 1; i--){
	 	
	 	if(i == N){
	 		cout << v2[i];
		 }else{
		 	cout << ' ' << v2[i]; 
		 }
	 	
	 }
		

}

在这里插入图片描述

加油 陌生人 ,学习就是就像分治算法一样,将一个大问题分解成小问题,然后逐个解决,最后的结果合并成大问题的解,

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-10-08 11:36:46  更:2021-10-08 11:37:33 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/20 5:04:58-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码