Codeforces Round #297(div 2) E. Anya and Cubes (双向dp)

题目

  • 【题目大意】:给你 n 个数和 k 根魔法棒,魔法棒可以把数字变成它的阶乘,比如 5 + = 5 = 120 。然后给你一个S,问:用这 n 个数,顶多有 k 个数可以变成阶乘,最后有几种方案可以凑出S这个数 ( 1 n 25 , 0 k n , 1 S 10 16 ) 数值范围( 1 a i 10 9 )

思路

枚举(错误):

用dfs遍历枚举每种情况,每个数有3种状态,那总共是 3 2 5 ,虽然知道会T,但是我还是抱着侥幸的心理写了一发,差不多没有剪枝的T在4,然后我加了个目标函数的剪枝,T在17,恩……没戏了

动态规划:

  • 【状态表示】: d p ( i , j , k ) 表示 d p a i 时,有 j 个数变成了阶乘,此时总和为 k 的方案数
  • 【状态转移方程】:
    d p ( i , j , k ) = d p ( i 1 , j , k a i ) + d p ( i 1 , j 1 , k f [ a i ] ) + d p ( i 1 , j , k )
  • 【问题以及解决方案】:

问题1
S 的范围: 1 S 10 16 ,用数组不能存储

解决方法:
d p ( i , j ) 作为一个map来存储

map <llong, llong> dp[maxn][maxn];

问题2:
d p 的初始化: d p [ 0 ] [ 0 ] [ 0 ] = 1 ;没有数选择,总和为0,为1种方案,这里思考一个问题,如果是上面那个状态转移问题,会出现什么问题?就是你枚举到dp(i, j, k)的时候, d p ( i 1 , j , k a i ) d p ( i 1 , j 1 , k f [ a i ] ) d p ( i 1 , j , k ) 这三个状态可能并没有求出来

解决方案
从小到大dp,由小状态去推大状态。

dp[i+1][j][key+a[i+1]] += dp[i][j][key];        //选了a[i]
dp[i+1][j][key] += dp[i][j][key];               //没有选a[i]
dp[i+1][j+1][key+f[a[i+1]]] += dp[i][j][key];   //选了a[i]的阶乘

问题3:
d p 的状态还是有点多,最多最后一维可能还是有 3 2 5

解决方案:
n = n >> 1 , 左边n可以 d p 一半,右边 d p 一半。最后合并。

问题4:
如何合并?

解决方案:
d p 1 ( i , j , k ) i 是固定的,枚举 j k , d p 2 ( x , y , z ) , x 是固定的,最后枚举一个 m 表示总共有多少个变成了阶乘。所以 y = m j , z = S k .

问题5:
如何遍历map的键值?

解决方案:
百度。

map<llong, llong>::iterator it;
llong key;

for (it=dp[i][j].begin(); it!=dp[i][j].end(); ++it)
    {
        key = it->first;
        ...
        dp[i+1][j][key+a[i+1]] += dp[i][j][key];
        ...
    }

算法流程

  1. 预处理阶乘。
  2. 输入数值,dp的初始化
  3. 分一半左右各自dp
  4. 合并dp
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<map>
#include<algorithm>
using namespace std;

typedef long long llong;
const llong INF = 1e16;
const int maxn = 25 + 5;
llong a[maxn];
llong f[maxn];
map<llong, llong>dp[maxn][maxn];

void Pre_frac()
{
    f[1] = 1;
    for (int i=2; i<20; ++i)
    {
        f[i] = f[i-1] * i;
        //printf("f[%d]=%lld\n", i, f[i]);
    }

    return;
}

void debug(int n)
{
    llong key;
    map<llong, llong>::iterator it;
    for (int i=1; i<=n; ++i)
        for (int j=0; j<=i; ++j)
            for (it=dp[i][j].begin(); it!=dp[i][j].end(); ++it)
            {
               key = it->first;
               printf("dp[%d][%d][%lld]=%lld\n", i, j, key, dp[i][j][key]);
            }

}

int main()
{
    Pre_frac();

    int n, m;
    llong s;
    while (scanf("%d %d %lld", &n, &m, &s) != EOF)
    {
        for (int i=1; i<=n; ++i)
            scanf("%lld", &a[i]);

        int half = n >> 1;

        for (int i=0; i<maxn; ++i)
            for (int j=0; j<maxn; ++j)
            dp[i][j].clear();

        map<llong, llong>::iterator it;
        llong key;
        dp[0][0][0] = 1;
        for (int i=0; i<half; ++i)
            for (int j=0; j<=i; ++j)
                for (it=dp[i][j].begin(); it!=dp[i][j].end(); ++it)
                {
                    key = it->first;                    
                    if (key>s)
                        continue;

                    dp[i+1][j][key+a[i+1]] += dp[i][j][key];
                    dp[i+1][j][key] += dp[i][j][key];
                    if (a[i+1]<19 && (key + f[a[i+1]] <= INF))
                      dp[i+1][j+1][key+f[a[i+1]]] += dp[i][j][key];
                }



        dp[n+1][0][0] = 1;
        for (int i=n+1; i>half+1; --i)
            for (int j=0; j<=(n-half); ++j)
                for (it=dp[i][j].begin(); it!=dp[i][j].end(); ++it)
                {
                    key = it->first;
                    if (key > s)
                        continue;

                    dp[i-1][j][key+a[i-1]] += dp[i][j][key];
                    dp[i-1][j][key] += dp[i][j][key];
                    if (a[i-1]<19 && (key + f[a[i-1]] <= INF))
                        dp[i-1][j+1][key+f[a[i-1]]] += dp[i][j][key];
                }

        //debug(n);

        llong ans = 0;
        for (int j=0; j<=half; ++j)
            for (it=dp[half][j].begin(); it!=dp[half][j].end(); ++it)
                for (int k=0; k<=m; ++k)
                {
                    key = it->first;
                    if (j <= k)
                        ans += dp[half][j][key] * dp[half+1][k-j][s-key];
                }

        printf("%lld\n", ans);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_35414878/article/details/79683229