[进阶] project euler 240 答案和代码请反选全文

project euler 240
http://projecteuler.net/index.php?section=problems&id=240


有五个6面的骰子, 同时抛掷, 最大的3个骰子和是15的抛掷方法有1111种,
几个例子:
D1,D2,D3,D4,D5 = 4,3,6,3,5
D1,D2,D3,D4,D5 = 4,3,3,5,6
D1,D2,D3,D4,D5 = 3,3,3,6,6
D1,D2,D3,D4,D5 = 6,6,3,3,3

如果现在有20个12面的骰子,那么最大的10个的和式70的抛掷方法有多少种?

//答案 ans = 7448717393364181966

一开始的想法:
记忆化搜索.
每一层dfs选一个1~12之间的数,然后如果某种组合已经把被记下了,那么就可以剪枝了.
需要注意的是,一共有20个骰子,就可以弄一个20进制,20的x次方,就表示选了x+1这个数多少次.
但是这么做会超long long,然后我就用了两个long long来做.

过了之后,看到了已故大神rem的提示,发现写的太暴力了,竟然过了....
time cost:60s +

typedef long long LL;
typedef pair<LL, LL> pll;
typedef map<pll, LL> mll;
pll operator + (const pll &a, const pll &b)
{ return pll(a.fi + b.fi, a.se + b.se); }

mll memo;
int sum_exp = 70;
int tar = 20;
int dice = 12;

int st[32], cnt[32];

pll carry[32];

int summ(int dep)
{
    int sum = 0, num = 0;
    for (int i = dice;i >= 1 && num < 10;i--) {
        if (cnt[i] == 0) { continue; }
        if (num + cnt[i] >= 10) {
            sum += (10 - num) * i;
            break;
        }else {
            sum += cnt[i] * i;
            num += cnt[i];
        }
    }
    return sum;
}

LL dfs(pll code, int sum, int dep)
{
    if (sum < 70 && 70 - sum > (tar - dep) * 12)
    {
        return 0;
    }

    if (dep <= 10) {
        if (sum > 70) {
            return 0;
        }
    }
    else if (sum > 70){ // dep > 10
        if (summ(dep) > 70) {
            return 0;
        }
    }

    mll::iterator idx = memo.find(code);
    if (idx != memo.end()) {
        return idx->second;
    }

    if (dep < 4) {
        printf("dep = %d\n", dep);
    }
    if (dep == tar) {
        return summ(dep) == sum_exp;
    }

    LL ans = 0;
    for (int i = 1;i <= 12;i++) {
        st[dep] = i;
        cnt[i] += 1;
        ans += dfs(code + carry[i], sum + i, dep + 1);
        cnt[i] -= 1;
    }
    memo[code] = ans;
    return ans;
}


int main()
{
    carry[1] = pll(0, 1);
    for (int i = 2;i <= 10;i++) {
        carry[i] = pll(0, carry[i-1].se * 20);
    }
    carry[11] = pll(1, 0);
    for (int i = 12;i <= 20;i++) {
        carry[i] = pll(carry[i-1].fi * 20, 0);
    }

    for (int i = 1;i <= 20;i++) {
        printf("carry[%d] = %lld, %lld\n", i, carry[i].fi, carry[i].se);
    }

    LL ans = dfs(pll(0, 0), 0, 0);
    printf("ans = %lld\n", ans);

    //AC 7448717393364181966
    return 0;
}


生成函数:
dfs每一层选depth大的数选了多少个,之后枚举出集合来,然后这个集合的求出这个集合的排列数,
相当于求了生成函数的多项式系数.之后把符合规律的加和即可.不加剪枝也不慢,加上剪枝就巨快了.
time cost:0.6s
代码
typedef unsigned long long LL;

int cnt[32];
LL fac[32], ans;

int summ(int tar = 1)
{
    int sum = 0;
    for (int i = 12, left = 10;i >= tar && left > 0;i--) if(cnt[i]) {
        int  y = min(cnt[i], left);
        sum  += i * y;
        left -= y;
    }
    return sum;
}

void dfs(int left, int sum, int dep)
{
    if (left * dep + sum < 70 ) { return; }

    if (dep == 0) {
        if (left != 0) { return; }
        if (summ() == 70) {
            LL t = fac[20];
            for (int i = 12;i >= 1;i--) { t /= fac[cnt[i]]; }
            ans += t;
        }
        return;
    }
    for (int i = 0;i <= left;i++) {
        cnt[dep] = i;
        if (left > 10 && summ(dep) > 70) { return; }
        dfs(left - i, sum + i * dep, dep - 1);
    }
}


int main()
{
    fac[0] = 1;
    for (int i = 1;i <= 20;i++) {
        fac[i] = fac[i-1] * i;
        printf("fac[%d] = %llu\n", i, fac[i]);
    }
    dfs(20, 0, 12);
    printf("ans = %llu\n", ans);
    return 0;
}

posted on 2011-04-27 14:59  schindlerlee  阅读(525)  评论(0编辑  收藏  举报