划分数dp 小结

小结划分数如何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;
}

未完待续。。。。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值