The 19th Zhejiang University Programming Contest Sponsored by TuSimple (Mirror) B"Even Number Theory"(找规律???)
题意:
给出了三个新定义:
- E-prime : ∀ num ∈ E,不存在两个偶数a,b,使得 num=a*b;(简言之,num的一对因子不能全为偶数)
- E-prime factorization : 定义集合P由 E-prime 元素组成,定义 e = p1*p2*.....*pn;(p1,p2,....,pn ∈ P , |P| = n)
- E-factorial : 定义 e!! = 2*4*6*8*.........*e;(简言之,偶数e及其之前的偶数连乘积)
输出一个数 e ,求满足条件2的集合P,并且集合P需满足:p1*p2*.....*pn = e!! , |P| = n;
求 n 的最大值;
题解:
定义集合 Pi 为偶数 i 的满足条件(2)的最大的集合;
例如P8 = {2,2,2},| P8 | = 3;
根据贪心的思想,要想使集合 Pe!! 中的元素个数最多,那么,e!! 一定要优先分解出最多的2(最小的e-prime);
定义 odd 表示奇数,那么 odd*2 为 e-prime,因为 odd 因式分解肯定没有偶数因子,odd*2 只能分解出一个偶数因子2,符合条件(1);
因此,对于所有的奇数 odd,可以通过让 odd*2 将 odd 转化为 e-prime,那么,我们的关注点就变成了 e!! 最多能分解出多少个2;
对于 e 之前的所有偶数,假设 ≤ e 的最大的2的幂为 2x+1,那么对于 e 之前的所有2的幂 21,22,23,....,2x+1 其可分解出 1+2+3+......+x+1 个2;
那 e 之前的所有非2的幂的偶数 even ,该如何快速求出他们的乘积最多能分解出多少个2呢?
首先,求解一下 2x+1 及其之前的偶数乘积最多可以分解出多少个2;
首先考虑这一点,任何一个偶数 even 都可分解成 odd*2i 模式, 那么 |Peven | = i;
那么,我们反过来考虑,对于任意奇数 odd 都可通过 odd*2i 求出 | Podd*2i | = i;
(每两个相邻的2的幂间的偶数块用红色编号① ② ③ ④ ...........表示)
(紫色数字代表相邻的2的幂间的奇数的个数)
对于偶数块①:只有一个奇数 3;
3*21 来到偶数块②;
3*22 来到偶数块③;
3*23 来到偶数块④;
........
3*2x-1来到偶数块(x);
(注意偶数块的编号和2的幂的关系)
可知,通过3*2i 可求出P3*21 , P3*22 , .........., P3*2x-1,其集合元素个数总和为 1+2+3+..........+(x-1);
那么,对于偶数块 2 中的某一奇数 odd 呢?
根据对3的分析,可知,偶数块②可以构成的 ≤ 2x+1 的最大的偶数为 odd*2x-2 ,那么,通过 odd 求出的集合Podd*2i 的元素个数总和为 1+2+3+.......+(x-2)
因为偶数块②有21个奇数,所以这些奇数可以求出的集合P的元素个数总和为 2*(1+2+3+........+(x-2) );
偶数块③有22个奇数,其中任意一个 odd 可构成的 ≤ 2x+1 的最大的偶数为 odd*2x-3 ,这些奇数可以求出的集合P的元素个数总和为 22*(1+2+......+(x-3) );
..............
偶数块(x-1)有2x-2个奇数,其可求出的集合P的元素个数总和为2x-2;
综上,2x+1 及其之前的偶数乘积最多可以分解出的2的个数为:
这几项加和的值等于 2x+1-1;
(并不是通过公式化简成这个最简式,而是通过打表找到的,公式化简的话,只能依靠那些大佬了QWQ)
打表推公式代码:(明天张贴)
1 #include<iostream> 2 #include<cstdio> 3 using namespace std; 4 5 int Sum(int pow,int up) 6 { 7 int sum=0; 8 for(int i=1;i <= up;++i) 9 sum += i; 10 return sum*pow; 11 } 12 int main() 13 { 14 int x; 15 while(~scanf("%d",&x)) 16 { 17 int sum=Sum(1,x); 18 int base=1; 19 for(int i=1;i <= x-2;++i) 20 { 21 sum += Sum(base,x-1-i); 22 base *= 2; 23 } 24 printf("sum=%d,%d\n",sum,1<<x); 25 } 26 }
那么,如果 e 为2的幂,直接输出 e-1;
如果不是,先求出 ≤ e 的最大的2的幂 2x+1 ,记录当前答案 ans = 2x+1-1;
那[2x+1 , e]之间的解该如何求出呢?
还是考虑 odd*2i 的模式;
令 odd1 ∈ [ 2x+1 / 2 , e / 2],∀odd1 * 2 ∈ [ 2x+1 , e ],且为 e-prime,ans += tot1*1;(tot为奇数个数)
令 odd2 ∈ [ 2x+1 / 4 , e / 4],∀odd2 * 4 ∈ [ 2x+1 , e ],且为 e-prime,ans += tot2*2;
.............
令 oddk ∈ [ 2x+1 / 2k , e / 2k],∀oddk * 2x ∈ [ 2x+1 , e ],且为 e-prime,ans += totk*k;
结束的条件是 2x+1 / 2k = e / 2k;
AC代码:(C++版大数伪码)(明天张贴)
1 #include<iostream> 2 #include<cstdio> 3 using namespace std; 4 #define BigInteger long long 5 6 BigInteger e; 7 BigInteger base; 8 9 BigInteger F() 10 { 11 BigInteger curBase=2; 12 BigInteger ans=0; 13 BigInteger l=base/curBase; 14 BigInteger r=e/curBase; 15 for(int i=1;i < 4000;++i) 16 { 17 // cout<<l<<' '<<r<<endl; 18 if(l >= r) 19 break; 20 BigInteger tmp=(r-l)/2; 21 if(r&1) 22 tmp++; 23 ans=ans+tmp*i; 24 l=l/2; 25 r=r/2; 26 } 27 return ans; 28 } 29 void Solve() 30 { 31 base=1; 32 while(base*2 <= e) 33 base *= 2; 34 BigInteger ans=base-1; 35 36 if(base != e) 37 ans=ans+F(); 38 39 cout<<ans<<endl; 40 } 41 int main() 42 { 43 int test; 44 while(cin>>test) 45 { 46 while(test--) 47 { 48 cin>>e; 49 Solve(); 50 } 51 } 52 return 0; 53 }
AC代码:(Java BigInteger类)(明天张贴)
1 import java.math.BigInteger; 2 import java.util.Scanner; 3 4 public class Main { 5 6 static Scanner cin = new Scanner(System.in); 7 static BigInteger e,base; 8 static BigInteger zero=BigInteger.valueOf(0); 9 static BigInteger one=BigInteger.valueOf(1); 10 static BigInteger two=BigInteger.valueOf(2); 11 public static void main(String[] args) { 12 13 int test; 14 while(cin.hasNext()) { 15 test=cin.nextInt(); 16 for(int i=1;i <= test;++i) { 17 18 e=cin.nextBigInteger(); 19 Solve(); 20 } 21 } 22 } 23 private static void Solve() { 24 25 base=one; 26 while(base.multiply(two).compareTo(e) <= 0) 27 base=base.multiply(two); 28 // System.out.println(base); 29 BigInteger ans=base.subtract(one); 30 31 if(!base.equals(e)) 32 ans=ans.add(F()); 33 System.out.println(ans); 34 } 35 private static BigInteger F() { 36 37 BigInteger curBase=two; 38 BigInteger ans=zero; 39 BigInteger l=base.divide(curBase); 40 BigInteger r=e.divide(curBase); 41 for(int i=1;i < 4000;++i) { 42 43 if(l.compareTo(r) >= 0) 44 break; 45 BigInteger tmp=(r.subtract(l)).divide(two); 46 if(r.mod(two).intValue() != 0) 47 tmp=tmp.add(one); 48 ans=ans.add(tmp.multiply(BigInteger.valueOf(i))); 49 50 l=l.divide(two); 51 r=r.divide(two); 52 } 53 return ans; 54 } 55 }
今晚比较嗨,比较尽兴;
陪俺家宝宝乘轻轨去市里疯了一圈,返程的路上还做过了一站;
哈哈哈,要一直陪着宝宝啊*_*