[进阶] 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) 编辑 收藏 举报