HDU4427Math Magic (dp+滾動陣列)

bigbigship發表於2014-12-13

題目連結:

http://acm.hdu.edu.cn/showproblem.php?pid=4427


題意:

求選定k個數,k個數的和為n,最小公倍數是m的方案數,最後的結果mod 1000000007;


分析:

狀態轉移很好找,難的是自己去實現優化。

狀態轉移方程為 : dp[i+1][s+k][lcm(l,k)]+=dp[i][s][l];

第一維代表的是有多少個數,第二維代表的是這些數的和,第三維代表的是這些數的最小公倍數

因為開不了那麼大的陣列,因此我們要用滾動陣列,詳細請見註釋。


程式碼如下:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;

const int mod = 1000000007;

const int maxn = 1005;

inline int gcd(int a,int b)
{
    if(b) return gcd(b,a%b);
    return a;
}

int dp[2][maxn][maxn];
int fac[1000];
int lcm[maxn][maxn];

void init()//預處理1~1000內的任意兩個數的最小公倍數
{
    for(int i=1;i<maxn;i++)
        for(int j=i;j<maxn;j++)
            lcm[i][j]=lcm[j][i]=i*j/gcd(i,j);
}

int main()
{
    init();
    int n,m,k;
    while(~scanf("%d%d%d",&n,&m,&k)){
        memset(dp,0,sizeof(dp));
        int tmp = m, cnt=0,v=0;
        memset(dp,0,sizeof dp);
        for(int i = 1; i<=m; i++)//預處理出m的所有約數,這k個數一定是在m的約數裡面選的
            if(m%i==0) fac[cnt++]=i;
        for(int i=0; i<cnt; i++)//初始化
            dp[v][fac[i]][fac[i]]=1;
        for(int i=1; i<k; i++) //列舉長度
        {
            memset(dp[v^1],0,sizeof(dp[v^1]));
            for(int j=i; j<n; j++) //列舉sum
            {
                for(int p=0; p<cnt; p++) //列舉上一個狀態的公倍數
                {
                    int mul=fac[p];
                    if(!dp[v][j][mul])
                        continue;
                    for(int q=0; q<cnt; q++) //列舉因子
                    {
                        int tt=j+fac[q];//計算當前狀態的和
                        if(tt>n)
                            break;
                        int t = lcm[mul][fac[q]];//當前狀態的最小公倍數
                        dp[v^1][tt][t]+=dp[v][j][mul];//當前狀態等於之前所有可以達到這個狀態的狀態的和
                        dp[v^1][tt][t]%=mod;
                    }
                }
            }
            v^=1;
        }
        printf("%d\n",dp[v][n][m]);
    }
    return 0;
}


相關文章