递归与分治思想1

链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
 

小q的数列

小q最近迷上了各种好玩的数列,这天,他发现了一个有趣的数列,其递推公式如下:
f[0]=0 f[1]=1;        f[i]=f[i/2]+f[i%2];(i>=2)
现在,他想考考你,问:给你一个n,代表数列的第n项,你能不能马上说出f[n]的值是多少,以及f[n]所代表的值第一次出现在数列的哪一项中?(这里的意思是:可以发现这个数列里某几项的值是可能相等的,则存在这样一个关系f[n'] = f[n] = f[x/2]+f[x%2] = f[x]...(n'<n<x) 他们的值都相等,这里需要你输出最小的那个n'的值)(n<10^18)

利用递归的方法就不说了。这里主要来复习位运算。熟悉位运算的小伙伴会将  f[0]=0 f[1]=1;    f[i]=f[i/2]+f[i%2];(i>=2)  自动翻译为:不断把二进制的i右移一位(即i>>1=i/2),再看一下i是不是奇数(二进制最后一位是不是1),如果是就+1,那么任何f[i]都能化成f[1]+X,X等于 i的二进制中1的个数 - 1。故f[i]即为i的二进制中1的个数。根据规律可知,n'即为将所有1移至低位的数(例如,10的二进制为1010,则将所有1移至低位的结果为0011,就是3)。

错误点:scanf和printf使用时看好数据类型!!已经栽了无数回QAQ

当x的数据类型为long long时,右移需用1ll<<x,若1不定义成long long类型就可能变为int越界。

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
typedef long long ll;
ll t;
int main(){
	scanf("%lld",&t);
	while(t--){
		ll n,ans=0;
		scanf("%lld",&n);
		while(n){
			if(n&1) ans++;
			n>>=1;
		}
		printf("%lld ",ans);
		printf("%lld\n",(ll)(pow(2,ans))-1ll);//double
	}
	return 0;
}

链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
 

求逆序数

在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序。一个排列中逆序的总数就称为这个排列的逆序数。

比如一个元素个数为4的数列,其元素为2,4,3,1,则(2,1),(4,3),(4,1),(3,1)是逆序,逆序数是4

现在求给定数列的逆序数

对于递归思想,运用的典例便是归并排序,而归并排序的典例是求逆序数对。问题关键在于逆序数的分析。不妨举例来看:

位置        1        2        3        |        4        5

数列        1        4        7        |        2        6

指针         i                mid

很显然,可以利用双重循环对逆序对进行计数,但在数据范围较大时并不可取。当归并排序时,首先将1加入答案数组,很明显接下来应加入数字2 。这意味着2要跨越4、 7排到1之后,逆序对数+2。同理,6要移动至7之前,逆序对数+1 。可以得到所加逆序对数为mid-i+1 。要注意的是,mid之后的值只有大于前面的值才可以向前移动,因为两个相同大小的数不可以构成逆序对。

另外,显然冒泡排序时交换的总次数即为逆序对数。

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
typedef long long ll;
ll n,cnt,a[1005],b[1005];
void merge_sort(int l,int r){
	if(l==r) return ;
	int mid=(l+r)>>1;
	merge_sort(l,mid);
	merge_sort(mid+1,r);
	int p=l,q=mid+1;
	for(int i=l;i<=r;++i){
		if(q>r||p<=mid&&a[p]<=a[q])
			b[i]=a[p++];
		else
			b[i]=a[q++],cnt+=mid-p+1;
	}
	for(int i=l;i<=r;++i)
		a[i]=b[i];
	return ;
}
int main(){
	cin>>n;
	for(int i=1;i<=n;++i)
		scanf("%lld",&a[i]);
	merge_sort(1,n);
	cout<<cnt;
	return 0;
}

链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网

第k小数

给你一个长度为n的序列,求序列中第k小数的多少。1<=n<=10^7 。

按暴力算法,最快的排序——快速排序,也只能O(nlogn)。我们可以想:快速排序是以中间的某个数为基准点,左边变为都是小于它的数,右边变为都是大于等于它的数,立刻知道总有一半在排序后就不必再排了。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
int t,n,k,a[5000005];
inline int read(){
	int x=0,f=1;
	char c;
	c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		x=(x<<1)+(x<<3)+(c^48);
		c=getchar();
	}
	return x*f;
}
inline int finding(int l,int r,int k){
	if(l==r) return a[k];
	int i=l,j=r,mid=(l+r)>>1,x=a[mid];
	while(i<=j){
		while(x>a[i])i++;
		while(x<a[j])j--;
		if(i<=j){
			swap(a[i],a[j]);
			i++; j--;
		}
	}
	if(k<=j) return finding(l,j,k);
	else if(k>=i) return finding(i,r,k);
	else return a[k];
}
int main(){
	t=read();
	while(t--){
		n=read(); k=read();
		for(int i=1;i<=n;++i)
			a[i]=read();
		cout<<finding(1,n,k)<<endl;
	}
	return 0;
}

汉诺塔

已知A柱上有n个圆盘,求将所有圆盘从A柱移至C柱的具体方案。

操作大致可以分3步:1、将前n-1个圆盘从A移至B;2、将第n个圆盘从A移至C;3、将前n-1个圆盘从B移至C。而核心思想则是通过中转柱(非初始柱非目标柱)转移圆盘。由此可以得到递归代码。

还可由数学推导式子得出。已知要把n个圆盘从A移到C需分上述3步进行,则设f(n)为n个圆盘从A移到C的总步数,n-1个圆盘从A移到B或是从B移到C需f(n-1)步,第n个圆盘从A移至C需1步。故f(n)=2f(n-1)+1,推导可得f(n)=2^n -1 。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
int n,cnt;
void hano(int x,char start,char end,char mid){
	if(x==0) return ;
	hano(x-1,start,mid,end);
	printf("%c-->%c\n",start,end);
	cnt++;
	hano(x-1,mid,end,start);
}
int main(){
	cin>>n;
	hano(n,'A','C','B');
	cout<<cnt;
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值