SRM534-div1-2-EllysNumbers

来源:互联网 发布:onvif test tool 源码 编辑:程序博客网 时间:2024/05/01 02:55
zz: http://www.strongczq.com/2012/03/srm534-div1-2-ellysnumbers.html

题目原文:
http://community.topcoder.com/stat?c=problem_statement&pm=11787&rd=14727

题目大意:
     给定一个整数n(取值范围[2,10^18]),以及互不相等的正整数(取值范围[1,10^9])集合,集合大小取值范围为[1,500],问:有几种方式可以将n表示成集合中几个互质的正整数的乘积(可以是一个正整数)。

思路:
     这道题题目相当的简短,非常赞!
     看完题目,我首先是挖掘了一下“将n表示成几个互质的正整数的乘积”这个内容,其实包含了一个非常有意义的含义。比如n=x*y*z,如果将这几个数都进行质因子分解,那么我们可以发现x,y,z必然不会有公共的质因子,并且它们的质因子肯定属于n的质因子,且对应的指数也必须是相同的。所以正整数集合中的值肯定是不能乱取。这个分析结果先放着。
     再考虑一下解法,这一题比较明显可以看出来应该使用DP了。用sp[]表示这个正整数集合。DP的状态方程为f(pos,s),表示只考虑整数集合中pos之后的元素,有几种方法可以将s表示成互质的正整数的成绩。 题目的答案可以表示成f(0,n)。状态转移方程为:
  • 如果sp[pos]与n/s互质:f(pos,s) = f(pos + 1,s) + f(pos + 1, s / sp[pos]) 
  • 否则:f(pos,s) = f(pos + 1,s)
     状态边界是f(sp.length, 1)=1。
     现在分析一下数据规模,pos的取值范围可以是[0,500),s的取值范围...我擦...居然是[1,10^18]。这必须不靠谱的!别急,这个时候我们就需要用之前的分析结果。由于正整数集合中只有满足一定条件的元素才是有意义的,这个条件是:
  • 必须能被n整除
  • 所有的质因子的指数与n的质因子的指数相同。
     在dp之前,我们先把所有不符合以上条件的元素都干掉。把s进行质因子分解,则所得到的质因子肯定是n的质因子,且指数部分必然相同。假设n的质因子个数为p,则s最多只能取2^p个值。那么p会有多大,考虑最小的那一批质数的乘积:
     2*3*5*7*11*13*17*19*23*29*31*37*41*43*47=614,889,782,588,491,410
     总共15个质数,所以p肯定不会大于15,那么状态总数最大可以接近500×2^15,考虑最终的返回是long类型,所以f(pos,s)的取值也必须是long的,64MB内存必然超了。我一开始没有分析好,觉得貌似ok就写代码了,而且还是用HashMap<Long, Long>来存中间状态的递归DP实现,结果system test时轻松time out了。
     所以这道题的难点又是如何用省内存并高效的方式来保存中间状态了。其实考虑到s的可能取值那么少,完全可以想办法避免将其值作为状态保存。可以想到的一种表示状态的方法是,用二进制掩码来表示s是否包含某一个质因子,这样可以使用0到2^15来表示这些状态。那么可以使用long[500][2^15]来表示中间状态,为了节省内存,我们可以进一步发现,由于每一步f(pos,s)最多依赖于pos+1时的状态值,所以在迭代实现的DP算法中完全不需要记录500个pos下的状态,只需要2个,相互交替使用。所以只需要long[2][2^15]的内存消耗即可搞定。后来看官方题解发现,其实只需要long[2^15]就足够记录状态了,见代码中的注释。
     前面其实还有一个难题忽视掉了,那就是怎么对可以达到10^18的n进行质因子分解?本人所知道的比较土的方法只能以O(sqrt(n))的复杂度进行质因子分解,所以直接分解n必然要冒着超时的危险。所以这里需要用到一个小技巧,那就是对整数集合中的每个整数(经过前面的条件帅选的)进行质因子分解,得到所有的质因子(包括指数的值),然后把这些因子全都乘起来,如果等于n,那么就得到n的质因子分解结果了;如果不等于n那必须返回0了。

Java代码:
public class EllysNumbers {    private long gcd(long a, long b) {        return a == 0 ? b : gcd(b % a, a);    }    private boolean isOK(long n, int x) {        if (n % x != 0) {            return false;        }        n /= x;        if (gcd(n, x) == 1) {            return true;        }        return false;    }    private void findFactors(int x, Set<Integer> factorSet) {        for (int i = 2; i * i <= x; ++i) {            int f = 1;            while (x % i == 0) {                x /= i;                f *= i;            }            if (f != 1) {                factorSet.add(Integer.valueOf(f));            }        }        if (x != 1) {            factorSet.add(Integer.valueOf(x));        }    }    public long getSubsets(long n, String[] special) {        StringBuilder sb = new StringBuilder();        Set<Integer> factorSet = new HashSet<Integer>();        for (String s : special) {            sb.append(s);        }        String[] strSps = sb.toString().split(" ");        int cnt = 0;        int[] sp = new int[500];        for (String s : strSps) {            int x = Integer.parseInt(s);            if (isOK(n, x)) {                sp[cnt++] = x;                findFactors(x, factorSet);            }        }        int[] factors = new int[factorSet.size()];        long product = 1;        int c = 0;        for (Integer f : factorSet) {            product *= f.intValue();            factors[c++] = f.intValue();        }        if (product != n) {            return 0;        }        int[] mask = new int[cnt];        for (int i = 0; i < cnt; ++i) {            for (int j = 0; j < factors.length; ++j) {                if (sp[i] % factors[j] == 0) {                    mask[i] |= (1 << j);                }            }        }        long[] mem = new long[1 << factors.length];        mem[(1 << factors.length) - 1] = 1;        for(int p = cnt - 1; p >=0 ; --p){            for(int m = 0; m < (1 << factors.length); ++m){                if((m & mask[p]) == 0){                    //m状态只依赖于m | mask[p],而m | mask[p]肯定大于m,                    //所以mem[m | mask[p]]的值必然还是上一次迭代的结果,尚未被破坏                    mem[m] += mem[m | mask[p]];                }            }        }        return mem[0];    }  }

原创粉丝点击