【DP例题选讲】&因式分解问题-任何人都不适合放弃&

来源:互联网 发布:关于sql查询的实训报告 编辑:程序博客网 时间:2024/04/30 04:57

DP选讲-因式分解问题

  • DP选讲-因式分解问题
    • 写在前面
    • 题目讲解TIME
      • 题目描述
      • DFS-原地瞬间爆炸TLE
      • DFS优化-然而并没有什么不同
      • 部分记忆化-论怎么骗部分分值
      • 正确的DP存储方式
      • 最快-最正-最真的最优代码
    • 小结
    • END-OF-ALL


写在前面

掐指一算,因式分解问题应该是我思考时长最久的一道题。坦白来说,自从我踏进算法这一新世界的大门开始【就是搜索啦】,这一梦魇就一直盘旋在上方,让我十分的不快、愤怒、无奈。
但这没关系,我靠着自己坚定的步伐,一步一步的,将这道题斩在了前进的道路上,狠狠地。我并没有借助外部的力量:像是正解博客、或是已做出的同学。
我靠着自己的力量。我坚信:我可以做到。
尽管不是最优算法,但是,写写博客让大家知道我的心路历程,也挺好。


题目讲解TIME

题目描述

将大于1的自然数N进行因式分解,满足 N=a1*a2*……*am 编一个程序,对任意的自然数N,求N的所有形式不同的因式分解方案总数。
例如,N=12,共有8种分解方案,分别是:

12=12=62=43=34=322=26=232=223

输入
第1行:1个正整数N(N<=10^9)

输出
第1行:一个整数,表示N的因式分解方案总数

样例输入
12
样例输出
8

DFS-原地瞬间爆炸TLE

第一反应:构造所有方案数。
实现过程很简单:枚举2~n内n的因子i,将它放在当前一位上,递归枚举n/i。
想想当初的自己也是天真,居然天真地以为这样的算法非常高大上,然后用高大上的写法写出来后发现并不是那样的。
而是这样的:
“输入109
“等待一秒……”
“等待两秒……”
“等待三秒……”
“栈溢出警告”
“程序关闭”
“内心OS:(╯‵□′)╯︵┻━┻”
DFS果然是很万能……地超时。

int dfs(int n){    int k=0;    if(n==1) return 1;    for(int i=n;i>=2;i--)    {        if(n%i) continue;        k+=dfs(n/i);    }    return k;}//错误的示范方法

DFS优化-然而并没有什么不同

枚举因子的时候,其实然可以枚举n/2~n之间的因子i,然后得到n/i这个因子。这样,原本寻找因子的时间O(n)霎时间就变成了O(n),减少了不少
然而:如果算法本身出了问题,即使把这个算法优化到了极致,依然无用。

int dfs(int n){    int k=1;    if(n==1) return 1;    for(int i=n/2;i>=sqrt(n);i--)    {        if(n%i) continue;        if(n/i!=i) k+=dfs(n/i);//不要忘考虑平方根        k+=dfs(i);    }    return k;}//并无什么用的莫名之优化

部分记忆化-论怎么骗部分分值

学到DP时,再次看到了这道题,于是想到记忆化。
然而……然而更尬的问题出现了,109再怎么开数组也看不下啊(╯‵□′)╯︵┻━┻!
于是聪明的我出了一记十分的机制的招式【明明是损招】:只开一部分记忆化;另一部分如果超出了某个界限MAXN,就只能重复算咯。
啊哈哈我真是骗分达人【然而并没有AC(〃>皿<)】

const int MAXN=1000000;int f[MAXN+5];int Function(int x){    if( x <= MAXN && f[x] != 0) return f[x];    int res=1;    for(int i=x/2;i>=sqrt(x);i--)    {        if(x%i) continue;        if(x/i!=i) res+=Function(x/i);//不要忘考虑平方根        res+=Function(i);    }    if( x <= MAXN ) f[x]=res;    return res;}//骗分专用:部分记忆化

正确的DP存储方式

通过观察估算,我们其实发现:状态存储并不需要那么多。比方说:对于当前这个数据而言,如果它是奇数,那么所有2的倍数的状态是闲置着的,就会产生白白浪费了很多空间的情况。所以为了保证状态的高效存储——诸君,我有一个大胆的想法:
使用动态数组vector存储,映射表map找到状态是否存在过
那么,掌声欢迎AC代码!

map<int,int>m;vector<long long>f;long long Function(int x){    if(x==1) return 0;    if( m.count(x) ) return f[m[x]];    long long res=1;    for(int i=2;i<=sqrt(x);i++)        if( x % i == 0) res+=Function(i);    for(int i=sqrt(x);i>=2;i--)        if( x % i == 0 && i != sqrt(x) ) res+=Function(x/i);    m[x]=f.size();    f.push_back(res);    return res;}

最快-最正-最真的最优代码

总觉得用动态数组有些别扭
那,可不可以在没有DP之前就找到所有合法且可利用的状态呢?
答案是:显然可以
从状态转移方程来讲,一个数的转移是由它的因数转移而来的。
它的因数是由它的因数的因数转移而来的,总而言之也是它的因数
所以,我们为何不妨先预处理n的因数,再进行愉快地动规?

int f[10005],g[10005],len;void Find_Prime(int x){    f[len]=1;    for(int i=2;i<=sqrt(x);i++)        if( x % i == 0) f[++len]=i;    for(int i=len;i>=1;i--)        if( f[i]*f[i]!=x ) f[++len]=x/f[i];    f[++len]=x;}//寻找n的因数int Function(int x)//处理的是n的第x个因数{    if(g[x]) return g[x];    for(int i=1;i<=x;i++)        if( f[x] % f[i] == 0 )            g[x]+=Function(lower_bound(f,f+len+1,f[x]/f[i])-f);//因为因数序列是单调递增的,所以可以使用二分查找    return g[x];}...printf("%d",Function(len));

小结

如果说,人不遇到点什么困难,是不现实的
人总会遇到形形色色的难处,编程也是,因为我们并非圣贤,并非“生而知之者”
但是,如果遇到一点困难就退缩,遇到一道难题就四处查正解,如果不会自己动脑想问题,不去自己动脑想问题
那样的话,怎么会有自己攻克题目的自豪感?怎么不会充斥“一看题解就懂,然而自己想不出来的尴尬?”
正如标题所言:
你,我,在世界上的任何人
都不适合放弃

END-OF-ALL

就是这样,新的一天里,也请多多关照哦(ノω<。)ノ))☆.。~

原创粉丝点击