牛客练习赛87

A. 中位数

题目:

给出长度为n的数组a1,a2,…,an,做确切地k次操作,每次操作选择两个不同的正整数i,j使得ai=ai+aj​,并将aj​从数组中删除。
k次操作后序列的中位数最小可以是多少?
中位数:一个长度为m的序列,它的中位数是将这m数升序排列后第floor⌊2m+1​⌋个数。
输入描述:

输入包含T组测试用例,第一行一个整数T
每组测试用例第一行两个整数n,k
每组测试用例第二行n个整数a1 a2 … an

输出描述:

输出T行第i行为第i组测试用例的答案。

示例1
输入

1
5 1
4 3 5 1 2

输出

2

备注:

1≤T≤5,1≤ai​≤200000,2≤n≤200000,1≤k

考察:贪心。

思路:
问k次操作后的中位数最小是多少,考虑两种情况。
如果k次操作后只剩下一个数,那么这个数就是中位数,并且它等于所有数的总和。
else,由贪心的策略,先从小到大排序,一直保留前半部分,操作后半部分的数,就可以一直维持中位数在前面的较小的原数列中的数为中位数。
那么k次操作后的中位数就是长度为n-k的前半部分的中位数!

代码如下:

#include 
using namespace std;
typedef long long ll;
const int N = 200010;
int  t,n,k,a[N];
ll sum; 

int main(){
	cin>>t;
	while(t--){
		cin>>n>>k;
		sum=0;
		for(int i=1;i<=n;i++) cin>>a[i],sum+=a[i];
		sort(a+1,a+n+1);
		n-=k;//得到k次操作之后的序列长度 
		if(n==1) cout<

B.k小数查询

题目描述
牛牛学会了可持久化线段树,于是他开始向牛妹炫耀。牛妹很不屑,那你能解决下面这道k{k}k小数查询的问题吗:
给出长度为n{n}n的序列a1​,a2​,…,an​,有多少对不同的整数对(l,r)(l≤r)(l,r)满足r−l+1≥k且al,al+1,…,ar​中第k小的数是x?

输入描述:

第一行三个整数n,x,k。
第二行n{n}n个整数a1 a2 … an

输出描述:

一行一个整数表示答案。

示例1
输入

5 3 2
1 2 3 4 5

输出
复制

3

说明

区间[2,3],[2,4],[2,5]第2小的数都是3。

备注:

1≤x,k≤n≤200000
a1​,a2​,…,an​为1,2,…,n的一个排列。

**考察:**前缀和,左右扩展

思路:
要使x为序列中第k小数,就要保证这段序列中有k-1个数小于x。
以x为中心,左右扩展,分别计算出左右两边小于x的个数,在0-k-1范围内,
用乘积(左右均有贡献)的方式求出每个位置满足要求的个数,再累加。

代码如下:

#include 
using namespace std;
typedef long long ll;
#define N 200010
int n,x,k,a[N],pos;
ll l[N],r[N],ans;
int main(){
	scanf("%d%d%d",&n,&x,&k);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		if(a[i]==x) pos=i;//记录x的位置 
	}
	l[0]=1,r[0]=1;//自身,当k=1,序列中仅有x时,就是第1小数
	int num=0;
	for(int i=pos-1;i>=1;i--){
		if(a[i]<x) num++;//num代表在x左边的比x小的第num个数 
		l[num]++;//更新满足是num的区间长度 
	}
	num=0;
	for(int i=pos+1;i<=n;i++){
		if(a[i]<x) num++;
		r[num]++;
	}
	for(int i=0;i<=k-1;i++){//左右贡献累乘 
		ans+=l[i]*r[k-1-i];
	}
	cout<<ans<<endl;
	return 0;
}

C.牛老板

题目描述
牛老板(牛牛)是一个土豪,他有无穷数量的纸币,但他的纸币面值很奇怪:
牛老板纸币的面值要么为6i,要么为9i,其中(i∈[−∞,∞])且i为整数。
牛老板买了一架私人飞机售卖价格为X,牛老板希望在不找零的情况下用尽可能少的纸币付钱,请你帮牛老板计算至少需要多少张纸币。
输入描述:

输入包含T组测试用例,第一行一个整数T
接下来T行每行一个整数X

输出描述:

输出T行,第i行为第i组测试用例的答案。

示例1
输入

4
6
9
998244353
1000000007

输出

1
1
17
17

备注:

1≤T≤40,1≤X≤1e12

考察:
贪心,DFS,记忆化搜索

记忆化搜索:
https://www.luogu.com.cn/blog/interestingLSY/memdfs-and-dp

https://www.cnblogs.com/jyroy/p/10274414.html

记忆化搜索步骤:

f(problem p){
    if(p has been solved){
         return the result
    }else{
         divide the p into some sub-problems (p1, p2, p3...)
         f(p1);
         f(p2);
         f(p3);
         ...
    }

思路:
先例举几种简单情况,复杂情况用简单情况递归计算。

如果是小于6的面额,那么只能一元一元的付,需要x张。
如果刚好是6或9,那么就是1张。

如果碰到不能整除某个数的情况,那么先算出这个数除以6的i次方(或9的i次方,至于取谁,看最后谁最优),
那么能够整除的部分就只需要一张,
这是能够整除部分的最优解。接下来再对余数部分需要多少张递归计算,最后将整数部分和余数部分需要的面值加起来,
在两种情况(先取6i还是9i)中取最优值。

由于在计算x需要多少张时可能会顺便计算了x的余数需要多少张,因此为了提高效率,采用记忆化搜索。我们将x与至少需要多少张面值进行映射,如果是计算过的,直接输出。

Key:
这个题需要将x与所需张数进行映射,那么就需要map或者unordered_map,但是由于此题会出现在递归计算时顺便计算出其他x所需张数,那么我们就不需要重新计算,直接取结果就行。这就是记忆化搜索unordered_map提供了一个mp.count(x)函数,若存在键为x的,返回键值对的个数,若不存在返回0.所以一般用if(mp.count(x)) 。无论从查找、插入上来说,unordered_map的效率都优于hash_map,更优于map;而空间复杂度方面,hash_map最低,unordered_map次之,map最大。hash_map有点复杂,所以需要查找时,首选unordered_map。

代码如下:

#include 
using namespace std;
typedef long long ll;
unordered_map<ll,ll> mp;
ll qk(ll a,ll b){ 
	ll ans=1;
	while(b){
		if(b&1)  ans=ans*a;
		a=a*a;
		b>>=1;
	}
	return ans;
}
ll cal(ll x){
	if(x<6) return x;//6^0=1,需要x张1 
	if(x==6||x==9) return 1; 
	if(mp.count(x)) return mp[x];//如果有为x的面值,直接返回 
	ll num1=0,num2=0;
	ll i=1; 
    while(i<=x){
    	i*=6;
    	num1++;
	}
	num1--;
	i=1;
	while(i<=x){
		i*=9;
		num2++;
	}
	num2--;
	mp[x]=min(cal(x-qk(6,num1))+1,cal(x-qk(9,num2))+1);
	return mp[x];
}


int main(){
	int t;
	cin>>t; 
	while(t--){
		ll x;
	    cin>>x;
	    printf("%d\n",cal(x)); 
	}
	return 0;
}

你可能感兴趣的:(比赛,贪心,dfs)