Good Numbers (hard version)cf#595(Div.3)

本文介绍了如何解决一个数值问题,即找到大于等于给定数x的最小'好数','好数'定义为3的幂的和且指数不重复。文章详细阐述了三种不同的算法思路:三进制标书法、二分查找法和贪心策略,并提供了相应的C++代码实现。通过这些方法,作者展示了如何利用进制转换、二分查找和贪心策略高效地求解问题。
摘要由CSDN通过智能技术生成

目录

题目:

思路:

一、三进制标书

二、二分的思路

三、贪心


题目:

(还是想说vj的页面看着亲切)

题目大意:定义“好数”为3的幂的和,并且指数要不相同,给你一个数x,求>=x的最小“好数”

思路:

数字高达1e18,又是“幂的和”,这一连串的信息,窝居然没有想到“进制”QAQ,那么在了解要基于“三进制”来做,那么具体该怎样实现呢?拖拖拉拉可算弄懂了三种方法,在此记录一下,感谢雪长!感谢雪长感谢雪长!!!

一、三进制标书

第一种最容易接受,从低位向高位遍历,如果这一位是0或1,则这一位不变,如果超过1则这一位变成0(因为指数不能重复,即这个幂最多只选一次),下一位+1

注意的是,从低到高位进一处理后,后面的都要置0,否则不是最小的。

举个栗子:十进制数7,转成三进制是21,进一操作处理后是101(对应十进制10),而我们要的是100(对应十进制9)

#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
int t;
ll n,a[40];
void solve(){
	cin >>n;
	ll tp=n,ans=0;
	int k=0,f=-1;
	memset(a,0,sizeof(a));
	while(tp){
		a[k++]=tp%3;
		tp/=3;
	}
	for(int i=0;i<k;i++){
		if(a[i]>1){
			f=i;
			a[i]=0;
			a[i+1]++;
		}
	}
	if(f!=-1){ 
		for(int i=0;i<f;i++)a[i]=0;//一路进一后最低位的0变成1,其后所有的都置0 
	}
	tp=1;
	for(int i=0;i<=k;i++){
		ans+=tp*a[i];
		tp*=3;
	} 
	cout<<ans<<'\n';
}
int main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin >>t;
	while(t--){
		solve();
	}
	return 0;
}

二、二分的思路

第二种是二分思路,二进制的表示但是位权是3,这样的表示在定义域上不连续但是单增,所以可以二分.其实就是在暴力的基础上用了二分提速,一开始不懂的是get函数,它是把x分解成2进制,x就是在这样的表示下的第几个数,分解了以后就可以知道第x个数对应十进制是多大。这样的写法是判x的第i位是不是1,在这样的表示下,第1 即(1)_{_{2}}个数是1,第2 即(10)_{2}个数是3,第3即 (11)_{2}个数是4,第4即(100)_{2}个数是9......

#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(0),cout.tie(0);
using namespace std;
typedef long long LL;
LL n,a[40];
LL get(LL x){
    LL res=0;
    for(int i=0;i<=39;i++){//二进制形式下的x第i位是否为1(取) 
        if((x>>i)&1)res+=a[i];
    }
    return res;
}
void solve(){
    cin>>n;
    LL l=1,r=(1ll<<39)-1;
    while(l<r){
        LL mid=l+r>>1;
        if(get(mid)>=n) r=mid;
        else l=mid+1;
    }
    cout<<get(l)<<endl;
}
int main(){
    IOS;
    a[0]=1;
    for(int i=1;i<=39;i++)a[i]=a[i-1]*3;
    int t;cin>>t;
    while(t--){
        solve();
    }
    return 0;
}

三、贪心

第三种方法是贪心的思路,“先从大到小贪,如果贪完了还剩下一点,再加上符合条件能构造出的最小的值”,相当于二进制加一,111+1=1000,就是把后面的1改成0在前面的0改1.在此珍藏U老师的神图,感谢雪长感谢雪长感谢雪长!(你永远可以相信U老师~)

 

 下面第一篇题解用的vis数组模拟二进制,“其实也没有必要这样构造,直接贪心完把选的位数的二进制表示加一再算出来和就是答案了”,于是第二篇代码就用一个长整型的x来代替vis数组的作用。

#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
int t,ct=1,vis[40];
ll a[40],n;
void init(){
	a[0]=1;
	while(1){
		a[ct]=a[ct-1]*3;
		if(a[ct]>1e18)break;
		ct++;
	}
}
void solve(){
	cin>>n;
	memset(vis,0,sizeof(vis));
	ll sum=0,tp=n;
	for(int i=ct;i>=0;i--){
		if(tp>=a[i]){
			vis[i]=1;
			tp-=a[i];
			sum+=a[i];
		}
	}
	if(sum==n){cout<<n<<'\n';return;}
	for(int i=0;i<=ct;i++){
		if(vis[i])sum-=a[i];
		else{
			sum+=a[i];
			break;
		}
	}
	cout<<sum<<'\n';
}
int main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin >>t;
	init(); 
	while(t--){
		solve();
	}
	return 0;
}

 千万千万要注意移位时写 1ll 而不是1啊!防止溢出防止溢出!!(感谢雪长!www)

#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
int t,ct=1;
ll a[40],n;
void init(){
	a[0]=1;
	while(1){
		a[ct]=a[ct-1]*3;
		if(a[ct]>1e18)break;
		ct++;
	}
}
void solve(){
	cin>>n;
	ll sum=0,tp=n,x=0;
	for(int i=ct;i>=0;i--){
		if(tp>=a[i]){
			x|=(1ll<<i);
			tp-=a[i];
			sum+=a[i];
		}
	}
	if(sum==n){cout<<n<<'\n';return;}
	x+=1;
	sum=0;
	for(int i=1;i<=ct;i++){
		if(x&(1ll<<i))sum+=a[i];
	}
	cout<<sum<<'\n';
}
int main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin >>t;
	init(); 
	while(t--){
		solve();
	}
	return 0;
}

【唉,这道题人家分分钟过了,窝个小菜鸡折腾了好久捏qwq,不过也学到了挺多的感觉,还是要加油加油!再次拜谢雪长们~】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值