Cubes UVALive - 7708

Cubes UVALive - 7708

标签: 搜索


题目描述

戳这里
题目大意:给你一个数$n$,要求将$n$分解成$m$个数的立方和,要求$m$最小且输出方案

题解

很容易能想到这道题可以用记忆化搜索,设$f[i]$表示数字为$i$时最少分解次数。然后我们就枚举每个数的三次方,可以发现这个数最大不会超过$356$,然后用当前数$x$减去枚举数的三次方$i\ast i\ast i$作为下一层递归的数字,即为$x-i\ast i\ast i$。当发现$f[x]$的值已经存在时,我们就不用递归往下了,可以记录答案了。
那么本题最重要的剪枝其实就一个:最优性剪枝。
我们每次递归到$f[x]$存在时,就用一个$ans$记录当前已经统计到的答案(不一定最优),在$dfs$时记录一个参数$cnt$,表示当前已经用了$cnt$次,如果你发现$cnt+x/(i\ast i\ast i)>=ans$,那么就可以直接$break$掉了。
这里要强调的是,判定式必须用$>=$而不是$>$,虽然看似没什么区别,可能后者会计算次数多一点。但打表发现,当数字较大时,它的分解次数其实会很小,一般只有$4$或$5$次,而$cnt$多一次,递归次数会多大概一个数量级的次数,所以这个时间效率我们不能忽视。
eg:
对于$44777444$这个数据,如果判定式用$>$,那么程序大概会跑$4$亿次,而如果换成$>=$,程序只用跑$1600$万次。

代码

#include<bits/stdc++.h>
#define N 46000000
using namespace std;

int n, ass;
int f[N], to[N], wo[N], id[N];

bool cmp(int a, int b) {return a > b;}

int dfs(int x, int y, int fr, int c)
{ 
    if(y > ass) return N;
    if(f[x] != -1) 
    {
        if(ass > y + f[x]) ass = y + f[x];
        return f[x];
    }
    int ans = N, tt, ww, dd;
    if(y + max(1, x / (c * c *c)) >= ass) return N;
    for(int i = c; i >= 1; i --)
    {
        if(i * i * i > x) continue;
        int w = x / (i * i * i);
        if(y + w >= ass) break;
        int t = dfs(x - (i * i * i), y + 1, fr, i) + 1;
        if(ans > t) ans = t, tt = x - (i * i * i), ww = 1, dd = i;
    }
    if(ans != N) f[x] = ans, to[x] = tt, wo[x] = ww, id[x] = dd;
    return ans;
}

int main()
{
    memset(f, -1, sizeof(f));
    memset(to, 0, sizeof(to));
    f[0] = 0; ass = 20;
    for(int i = 1; i <= 356; i ++)
    {
        int x = i * i * i;
        f[x] = 1; to[x] = 0; id[x] = i; wo[x] = 1;
    }
    for(int i = 1; i <= 1e5; i ++)
        ass = 2000, f[i] = dfs(i, 0, i, 356);
    while(scanf("%d", &n) != EOF)
    {
        ass = 2000;
        int ans = dfs(n, 0, n, 356);
        printf("%d\n", ans); 
        int x = n, cnt = 0; 
        int b[20];
        while(x)
        {
            for(int i = cnt; i < cnt + wo[x]; i ++)
                b[i] = id[x];
            cnt += wo[x]; x = to[x]; 
        }
        sort(b, b + cnt, cmp);
        for(int i = 0; i < cnt; i ++) printf("%d ", b[i]);
        printf("\n");
    }
    return 0;
}

心得

有时候往往一个小细节就影响了程序的效率,这主要还是决定于题目的性质,不过我们还是尽量采用高效的方法来写代码,不放过优化的地方。


  转载请注明: iSecloud's Blog Cubes UVALive - 7708

 上一篇
Codeforces Round 580 (Div. 2) D Codeforces Round 580 (Div. 2) D
Codeforces Round #580 (Div. 2) D标签: Floyd 二进制拆分 题目描述戳这里 题解首先对于这种涉及二进制的运算我们容易想到二进制拆分,对于每一位我们开一个$vector$,表示有多少个值这一位为$1$,那
2019-08-22
下一篇 
uvalive 6902 Three Squares uvalive 6902 Three Squares
uvalive 6902 Three Squares标签: 二分 搜索 题目描述戳这里题目大意:给你n个点,请确定正方形的最小边长使得用三个这样的正方形可以覆盖n个点 题解首先可以发现答案具有单调性,可以二分答案正方形边长$len$,然后
2019-08-18