【学校内部OJ - 1205】因式分解问题

@因式分解问题@


@写在前面@

掐指一算,因式分解问题应该是我思考时长最久的一道题。
但这没关系,我靠着自己坚定的步伐,一步一步的,将这道题斩在了前进的道路上,狠狠地。我并没有借助外部的力量:像是正解博客、或是已做出的同学。
我靠着自己的力量。我坚信:我可以做到。【中二少年!是你!】
尽管我的做法不是最优算法,但是,写写博客让大家知道我的心路历程,也挺好。


@题目描述@

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

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

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

样例输入
12
样例输出
8

@算法1:DFS-原地爆炸瞬间TLE@

第一反应:构造所有方案数。
实现过程非常简单:枚举2~n内n的因子i,将它放在当前一位上,递归枚举n/i。
想想当初的自己也是天真,居然天真地以为这样的算法非常高大上,肯定是个优美的正解……然后用高大上的写法写出来后发现并不是那样的。
而是这样的:
“输入 10 9
“等待一秒……”
“等待两秒……”
“等待三秒……”
“栈溢出警告”
“程序关闭”
“内心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;
}//错误的示范方法

@算法1.5:DFS优化-然而并没有什么不同@

那时的我思考了一刻,突然!脑袋一亮【并不】想到一种优化!枚举因子的时候,可以枚举 n / 2 ~ n 之间的因子 i ,然后得到 n / i 这个因子。这样,原本寻找因子的时间O( n )霎时间就变成了O( n )!!!
我的天啊我真是个天才啊哈哈!
……
……
……
“内心OS:这TM不可能啊为什么它还是TLE(▼ヘ▼#)”

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;
}//莫名其妙的无用优化

优化无法让错误的算法AC。

@算法2:部分记忆化 - 只是用来骗部分分@

学到DP时,再次看到了这道题,于是想到记忆化。
然而……然而更尬的问题出现了, 10 9 再怎么开数组也看不下啊!
(╯‵□′)╯︵┻━┻去你的我还以为我想出了正解!
于是聪明的我出了一记十分的机制的损招招式:只开一部分记忆化;另一部分如果超出了某个界限LIM,就用DFS。
啊哈哈我真是骗分达人【然而这不是比赛,部分分没用

扫描二维码关注公众号,回复: 2167675 查看本文章
const int LIM=1000000;
int f[LIM+5];
int Function(int x)
{
    if( x <= LIM && 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 <= LIM ) f[x]=res;
    return res;
}//骗分专用:部分记忆化

@算法3:正确的DP方法.jpg@

通过观察估算,我们其实发现:状态数并不需要那么多。比方说:对于奇数而言,所有下标为偶数的状态是闲置着的,就会产生白白浪费了很多空间的情况。所以为了保证状态的高效存储——
使用动态数组vector存储,映射表map找到状态是否存在过
哇嘎嘎我真是机智!

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;
}//终于可以AC了

@算法4:这才是正解好吧@

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

@正确的代码打开方式@

#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
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];
}
int main()
{
    int n;
    scanf("%d",&n);
    Find_Prime(n);
    g[0]=1;
    printf("%d\n",Function(len));
    return 0;
}//AC从不是目的,弄懂才是目的

@有些中二的小结 ̄ω ̄=@

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

@END-OF-ALL@

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

猜你喜欢

转载自blog.csdn.net/tiw_air_op1721/article/details/78149422