CodeForces #431 Div. 2 849C From Y to Y 贪心 技巧题

来源:互联网 发布:火车采集器软件 编辑:程序博客网 时间:2024/06/06 01:25

做题之前首先大喊

     骑士团参见公主殿下!


这道题的题意:

首先定义了合并两个全小写字母的字符串的代价,∑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 <iostream>#include <algorithm>#include <cmath>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<tmp;++i)            cout<<(char)('a'+rec);        ++rec;        n-=tmp*(tmp-1)>>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个字母的情况下能够表示出来


原创粉丝点击