整數劃分(小結劃分數如何dp)

 原網址https://blog.csdn.net/guhaiteng/article/details/69218517小結劃分數如何dp

1.hdu 1028 整數劃分

首先,我們引進一個小小概念來方便描述吧,dp[n][m]是把自然數劃劃分成所有元素不大於m的分法,例如: 
當n=4,m=1時,要求所有的元素都比m小,所以劃分法只有1種:{1,1,1,1}; 
當n=4,m=2時,只有3種{1,1,1,1},{2,1,1},{2,2}; 
當n=4,m=3時,只有4種{1,1,1,1},{2,1,1},{2,2},{3,1}; 
當n=4,m=5時,只有5種{1,1,1,1},{2,1,1},{2,2},{3,1},{4}; 
從上面我們可以發現:當n==1||m==1時,只有一種分法; 
當n < m時,由於分法不可能出現負數,所以dp[n][m]=dp[n][n]; 
當n==m時,那麼就得分析是否要分出m這一個數,如果要分那就只有一種{m},要是不分,那就是把n分成不大於m-1的若干份;即dp[n][n]=1+dp[n][n-1]; 
當n>m時,那麼就得分析是否要分出m這一個數,如果要分那就{{m},{x1,x2,x3..(可能含有m)}}時n-m的分法dp[n-m][m],要是不分,那就是把n分成不大於m-1的若干份;即dp[n][n]=dp[n-m][m]+dp[n][m-1];

代碼:

#include<iostream>
using namespace std;
#define maxn 121
int dp[maxn][maxn]={0}; 
int main()
{
    int i,j;
    for(i=1;i<=121;i++)dp[1][i]=dp[i][1]=1;
    for(i=2;i<121;i++)
    {
        for(j=2;j<=121;j++)
        {
            if(i<j) dp[i][j]=dp[i][i];
            else if(i==j)dp[i][j]=1+dp[i][j-1];
            else if(i>j) dp[i][j]=dp[i-j][j]+dp[i][j-1];
        }
    }
    int n;
    while(scanf("%d",&n)!=EOF)printf("%d\n",dp[n][n]);
    return 0;
}

ps:這題也可以母函數做,參考: 
http://blog.csdn.net/guhaiteng/article/details/52904663

2.CDOJ 1307 ABCDE

題意: 
在數電中,有一種碼,類似BCD碼這種玩意兒 
第i位如果爲1的話,那麼ans+=a[i],a[i]是這一位的位權 
然後現在給你一個n,問你一共有多少種碼可以表示1~n的所有數呢? 
1,1,2和2,1,1視作一樣。

解法: 
DP預處理 
首先考慮這個東西,如果不視作一樣的話,就很簡單了

dp[i]表示當前和爲i的方案數,顯然這個玩意兒能夠一直轉移到2i-1去。(比如5,你轉移到最大的肯定是3,不然就無法表示出1,2,3,4,5每個數了,這很顯然)

由於視作一樣,那麼我們只要維護一個當前的最大值就好了,保證這個序列是遞增的,這樣就都不會一樣了。

dp[i][j]表示現在和爲i,最大值爲j的方案數有多少。

轉移方程: 
dp[i][j]=(dp[i−1][j−1]+dp[i−j][j])

前者表示由沒有最大值j轉移過來,後者表示從有最大值j轉移過來

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int mod = 1e9+7;
long long dp[5010][5010];
long long ans[5010];

int main()
{
    dp[0][0] = 1;
    for(int i = 1; i <= 5000; i++){
        for(int j = 1; j <= (i+1)/2; j++){
            dp[i][j] = (dp[i-1][j-1]+dp[i-j][j])%mod;
            ans[i] += dp[i][j];
            ans[i] %= mod;
        }
    }

    int T;
    scanf("%d", &T);
    while(T--){
        int n;
        scanf("%d", &n);
        printf("%lld\n", ans[n]);
    }
    return 0;
}

 

3.687C The values you can make 
題意:給n個各有價值的硬幣,要從它們中選出若干個組合成面值k,而要求的是各個方案裏這些選出的硬幣能組合出來的面值有哪些

dp[i][j][k]表示到第i個硬幣,和爲j,組成面值爲k這種情況是否存在。 
要用滾動數組寫,不然要爆內存

詳細可以看題解: 
http://codeforces.com/blog/entry/45770

代碼:

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

#define ff first
#define ss second
#define pb push_back
#define ll long long
#define mod 1000000007
#define ull unsigned long long
#define mst(ss,b) memset(ss,b,sizeof(ss));
#define pl(x) cout << #x << "= " << x << endl;
const int inf=0x3f3f3f3f;
const int N=505;

bool dp[2][N][N];//第i個 和爲j 組成面額爲k
int n, k, c[N], cnt;
int ans[N];

int  main(){
    scanf("%d%d", &n, &k);
    for(int i=1; i<=n; i++)scanf("%d", &c[i]);

    dp[0][0][0] = 1;
    for(int i=1; i<=n; i++){
        for(int j=k; j>=0; j--){
            for(int x=j; x>=0; x--){
                dp[i%2][j][x] |= dp[(i-1)%2][j][x];
                if(j-c[i] >= x)dp[i%2][j][x] |= dp[(i-1)%2][j-c[i]][x];
                if(x >= c[i])dp[i%2][j][x] |= dp[(i-1)%2][j-c[i]][x-c[i]];
            }
        }
    }

    for(int i=0; i<=k; i++){
        if(dp[n%2][k][i])ans[++cnt] = i;
    }
    printf("%d\n", cnt);
    for(int i=1; i<=cnt; i++)printf("%d ", ans[i]);
    return 0;
}

4.51nod 1201 整數劃分

思路: 
dp[i][j]表示i這個數劃分成j個數的情況數。 
dp[i][j] = dp[i - j][j] + dp[i - j][j - 1] 
前者表示將i - 1劃分爲j個數,然後j個數都+1 還是不重複 
後者表示將i - 1劃分爲j - 1個數,然後j - 1個數都+1,再加上1這個數 
普通的dp是O(n^2)的,但是可以發現1 + 2 + … + m = n , (1 + m)m = n 2,j只要遍歷sqrt(n * 2)個就好了。所以複雜度爲O(n*sqrt(n*2))

#include <bits/stdc++.h>
using  namespace  std;
#define mod 1000000007
template<class T> void read(T&num) {
    char CH; bool F=false;
    for(CH=getchar();CH<'0'||CH>'9';F= CH=='-',CH=getchar());
    for(num=0;CH>='0'&&CH<='9';num=num*10+CH-'0',CH=getchar());
    F && (num=-num);
}
const int N=5e4+10;

int  dp[N][351], n;

int  main(){
  read(n);
  dp[0][0] = 1;
  for(int i = 1; i < 350 ; i++){//i個數
    for(int j = 0; j <= n ; j++){//組成j
      if(j - i >= 0){
        dp[j][i] = (dp[j - i][i] + dp[j - i][i - 1]) % mod;
      }
    }
  }
  int ans = 0;
  for(int i = 1; i < 350; i++)
    ans = (ans + dp[n][i]) % mod;
  printf("%d\n", ans);
  return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章