Topcoder SRM616-Div1-Lv2 ColorfulCoins
涉及知识点:奇妙 Ad-hoc
前言
一道很不常规的题目,思维难度大代码简单,而且这种思路很难在赛时想到,故记录一下。
题意
某国的货币系统硬币有 \(n\ (\leq 60)\) 种面额 \(val_i\ (\leq 10^{18})\),其中最小的面额为 \(1\),并且所有的面额之间都保证两两有倍数关系,每种面额的硬币有独一无二的颜色,但你还不知道颜色与面额的对应关系。现在你可以在 ATM 机上取钱(取钱金额无限制),每次询问 ATM 机会返回 硬币数量最少 的硬币(即尽量用大面额的硬币),问至少需要多少次询问才能确定每种面额硬币的颜色。另外,输入的面额已提前从小到大排序。
思路
本题最核心的转换:我们对于每个询问的总金额 \(c_i\),ATM 返回的硬币视为一个 \(n\) 维向量,即每种面额 \(val_i\) 的硬币有多少个,记为 \(cnt_i\),此外还必须满足 \(\large cnt_i<\frac{val_{i+1}}{val_i}\),不然不满足硬币数量最少,可以理解成每位的“进制”不一样。记一共有 \(m\) 次询问,将这些 \(n\) 维向量组成一个 \(m\) 行 \(n\) 列的矩阵,不难发现最后我们需要找的询问方案需要满足任意两列之间至少有一处不一样,并且不存在全部为 \(0\) 的列。
我们对于不同的 \(\large\frac{val_{i+1}}{val_i}\) 拆开来思考,对于一组等比的面额子序列,记比值为 \(d\),可以发现查询 \(q\) 次最多能辨别出 \(d^q-1\) 种硬币,因为全零不合法所以减一,类比 \(q\) 位以内的 \(d\) 进制非零数一共有 \(d^q-1\) 种。
但是,我们发现,其实不用每种进制全部分开查询,一些查询可以合并,比如说 1 2 10 50
中 1 2
和 10 50
可以在一次同时查询,但是 1 2 6 18
不行。仔细观察为什么前者可以而后者不可以,会发现后者进制卡的很死导致无论如何分配硬币的数量都会有“进位溢出”的情况。
于是我们考虑去找这样的一个最小的 \(ans\),满足 \(\forall i\in [1,n-1], val_i^{ans}-1\geq i\),即为答案。关键点在于我们将比 \(val_i\) 小的进制全部当作 \(val_i\) 来处理,这样的话就能处理“进位溢出”,并且不用担心这样会让小进制无解,因为我们最终的答案是满足所有的 \(i\) 的 \(ans\) 的最大值。[1]
代码
下面有两份代码,第一份实际上是第二份的优化版本,如果你还是不太清楚,可以先看第二份代码辅助理解。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAXN=65;
int n,sum=0;
LL val[MAXN],ans=-1;
map<LL,int>cnt;
inline LL solve(LL a,LL b){//find the minimum k satisfying a^k-1 >= b
LL res=0,cur=1;
while(cur-1<b){
res++;
cur*=a;
}
return res;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>val[i];
}
if(n==1){
puts("1");
return 0;
}
for(int i=1;i<n;i++){
cnt[val[i+1]/val[i]]++;
}
for(auto it:cnt){
// cout<<it.first<<' '<<it.second<<endl;
sum+=it.second;
ans=max(ans,solve(it.first,sum));
}
cout<<min(ans,(LL)n);
return 0;
}
bool check(){
for(int i=1;i<n;i++)
if(qpow(d[i],m)-1<i) return false;
return true;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<n;i++) d[i]=a[i+1]/a[i];
sort(d+1,d+n);
ans=1;
while(!check()) ans++;
cout<<ans<<endl;
return 0;
}