做题之前首先大喊
骑士团参见公主殿下!
这道题的题意:
首先定义了合并两个全小写字母的字符串的代价,∑c∈{'a', 'b', ... 'z'} f(s,c)*f(t,c),其中f(x,y)代表y字母在串x中出现的次数,s和t为那两个待合成的字符串。
然后给你一个k,让你求出一个集合,使得这个集合里的字符串合并为1个字符串之后的最小代价恰好为k。
我们首先考虑n个同样的字符合成一个字符串的最小代价。
一种方法是首先合成长度为2,然后再合成长度为3的串,再合成长度为4的串....即每一次都把一个长度为1的串和最长的串合并。
代价是∑i(1->n-1)i = n*(n-1)/2
另外一种考虑的方法是,首先把串都合成长度为2的串,然后把长度为2的串都合成为长度为4的串.......即每一次都让剩下的串的长度都一样(此处为了计算方便起见,考虑n为2的整数倍)
那么这样的代价是:
第一次合成: 1*n/2
第二次合成: 4*n/4
第三次合成: 16*n/8
.......
等比数列求和一下发现也是n*(n-1)/2
实际上对于任意一种合成方式代价都是n*(n-1)/2
证明放在附录1
然后,我们需要一个贪心的做法
每一次构建一个新的字符串,使得该字符串在当前所有能够合成的字符串中消耗的代价是最大的
即每一次都从剩余cost里面拿掉最多的cost,使得剩余能用的cost尽量的小。
关于这个贪心的大致正确性放在附录2
以下是代码:
利用upper_bound每一次二分查找当前能够拿掉的最大长度,更新n,不断迭代。
此外特判一下n==0的情况
不熟悉upper_bound的朋友: http://www.cplusplus.com/reference/algorithm/upper_bound/
想顺便看看lower_bound的朋友: http://www.cplusplus.com/reference/algorithm/lower_bound/
#include
#include
#include
using namespace std;
const int maxm=100000,len=sqrt(maxm);
int n,arr[len+10],tmp,rec;
int main(){
for(int i=0;i<=len;++i)arr[len-i]=i;
ios_base::sync_with_stdio(0);
cin>>n;
if(n==0)
cout<<"a";
while(n){
tmp=*upper_bound(arr,arr+len+1,n,[](int m,int k){return m>=(k*(k-1)>>1);});
for(int i=0;i>1;
}
cout<<"\n";
return 0;
}
附录1:
有关于代价的一致性的证明:
其实根据刚刚的做法,我们应该能够猜测出来代价是一致的。
证明可以用数学归纳法,
假设对于长度为x的代价是f(x)=x*(x-1)/2
那么考虑长度为n的串,对于任意一种分割方式,分为长度为x和y两个串,有x+y=n
f(n)=f(x)+f(y)+x*y ->f(n) = n*n(n-1)/2
Q.E.D
附录2:
有关于贪心正确性的大致证明
考虑最坏的情况,即拿掉最大长度后剩下的串最多
假设n = k*(k-1)/2 -1 即 n = O(k^2), 按照我们的算法,我们只能够拿长度为k-1的串,因此
n - (k-1)*(k-2)/2 = k - 2 = O(k) 即原来是k^2阶现在变为了k阶,即由n -> sqrt(n)
即每一次n会变为sqrt(n)
那么对1E5做sqrt 26次后已经和1十分接近了,因此我们有理由认为能够在不超过26个字母的情况下能够表示出来