Wonderful

For an integer n, we call k>=2 a wonderful base of n, ifall digits of n base k are 1.

Now given a string representing n, you should return the smallest goodbase of n in string format.

Example 1:

Input: "13"

Output: "3"

Explanation: 13 base 3is 111.

Example 2:

Input: "4681"

Output: "8"

Explanation: 4681 base8 is 11111.

Example 3:

Input:"1000000000000000000"

Output:"999999999999999999"

Explanation:1000000000000000000 base 999999999999999999 is 11.

Note:

  1. The range of n is [3, 10^18].
  2. The string representing n is always valid and will not have leading zeros.


        校赛就这么结束了,虽说带病参赛,但还是情理之中拿了个第一……

        题目虽然不难,但是其中也暴露出了很多问题。可能是状态原因或者是什么别的,反正好几道题目思路完全正确,但是却是错了很多次才A了,导致罚时特别多……

        除此之外这次比赛中我们的破题速度还是很值的肯定的,在比较短的时间内就几乎把所有题目的算法预料到了。但还是那个不稳的问题,有两到模拟题没有做,还有一个二分答案怎么也a不过……

        不管怎么说,都已经过去了,积累经验吧,至少我们这次没有死磕一道题目,哪一道题目WA了一时没发现错误,就先发现做下一道题。当然了,前提是要有题可作。西安的regional邀请赛应该是稳去了,至于成绩的话还是不敢抱太大希望,但是怎么说要保铜争银吧……剩下的时间一起加油吧……生病也不能停止写代码……

        现在来说说这道题吧,刚刚和学长确认了,不是原创题,但还是不得不赞一下选题人,弄了这么一道有意思的题目。可以说做题的过程真的是一波三折,挫折此起彼伏啊。首先,看到这题第一反应肯定是要用二分答案的,但是二分的前提是要有单调性。数字n分成某一个进制后,在该进制底下所有位都是1,很显然,当进制和位数确定后,可以加权求得一个数字,将这个数字与n比较。这个加权和就是等比数列的和,在最开始的阶段,我们选择用求和公式来表示。这样就有这样一个方程n=(q^(k+1)-1)/(q-1)。只要求这个方程的解即可,其中q为进制,k为位数。于是我开始寻找单调性,我运用了求导、放缩甚至泰勒展开都想到了,但是还是无法找到单调性,因为这个k其实也是一个关于x的函数,而且它是离散的。

        你可能觉得好像已经没辙了(我当时也几乎绝望),突然灵感一来,如果k固定为常数的话,那么这个q是否就有单调性呢?简单求导后发现当k>=2时,方程右边的函数确实是单调递增的。然后发现k的取值范围恰好是[2,log(N)],完美的符合。于是开始敲代码啦……但是敲完之后发现小数据可以过,但是大数据却过不了。又经过一番纠结之后,我发现,当进制比较大的时候求等比数列和的时候会超过算术上限。于是加了一个特判,当n在某个进制下的位数达不到二分要求的位数的时候,不计算和,直接返回偏大。这样看似把大的样例给过了,但是交上去还是WA。于是开始意志的考验了,这道题被放在一边,队友开始码下一题,然而我苦苦寻找着错误。我开始自己构造数据,小的进制都没有问题,当我构造了一个1e8进制的数字的时候,代码开始错了。最后发现,是等比数列求和公式的问题,根据方程n=(q^(k+1)-1)/(q-1),当q=1e8的时候,k最小取2,那么k+1就是等于3了,1e8^3当然超过算数上限了。错误就在于用了求和公式,如果裸的直接求和最高次就只有q^2,这个在可接受的范围之内不会超上限。

        最后就是一些细节上的东西了,pow的话,当返回结果比较大的时候会出现误差,所以求次方的时候最好也是人工计算。复杂度的话大概是O(logNlogNlogN),枚举klogN,二分进制logN,计算等比数列和logN。再加上Kase,也几乎刚好在可承受范围内。到后面发现那个检查n在相应进制下的位数那个特判好像可以删掉。具体代码如下:

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<iomanip>

using namespace std;

unsigned long long n,l,r,mid;

int bit(unsigned long long m,int k)
{
int p=0;
while (m) {p++; m/=k;}
return p;
}

unsigned long long f(unsigned long long x,int k) //计算等比数列和
{
if (x!=1&&bit(n,x)<k) return n*n; //现在发现好像没有什么用
unsigned long long ret=0,m=1;
for(int i=0;i<k;i++)
{
ret+=m; m*=x; //直接用pow会错而且好像更慢
}
return ret;
}

int main()
{
while(cin>>n)
{
int p=bit(n,2);
bool flag=0;
for(int i=p;i>1&&!flag;i--) //枚举位数i,要求进制小的优先,故位数从最多开始枚举
{
l=2,r=n;
while (l<=r) //二分进制
{
mid=(l+r)>>1;
unsigned long long y=f(mid,i);
if (y==n) {cout<<mid<<endl;flag=1;break;}
else if (y>n) r=mid-1;
else if (y<n) l=mid+1;
}
}
if (!flag) cout<<n-1<<endl;
}
return 0;
}