ACM动态规划模板(更新ing...)

  • 最长上升子序列问题
  • 多重背包问题
  • 多重部分和问题
  • 划分数问题
  • 多重集组合数问题

1、最长上升子序列问题

题目:有一个长为n的数列a0,a1,…,an-1。请求出这个序列中最长的上升子序列的长度。上升子序列指的是对于任意的 i< j 都满足ai< aj 的子序列。
思路: 定义dp[i]为长度为i+1的上升子序列中末尾元素的最小值(不存在的话为INF)

//最长上升子序列问题
int dp[Max_n];

void solve(){
    memset(dp,0x3f,sizeof(dp));
    for(int i=0;i<n;i++)
        *lower_bound(dp,dp+n,a[i])=a[i];
    printf("%d\n",lower_bound(dp,dp+n,inf)-dp);
}

2、多重背包问题

题目:有n种重量、价值和数量分别为wi,vi,ci的物品,从这些物品中挑选出总重量不超过W的物品,求出挑选物品价值总和的最大值。(1≤n≤100,1≤W≤50000)
思路:二进制优化多重背包。

//多重背包问题
int n,W;
int w[Max_n],v[Max_n],c[Max_n]; //重量、价值和数量
int dp[Max_W];

void ZeroOne_Pack(int w,int v){ 
    for(int i=W;i>=w;i--)
        dp[i]=max(dp[i],dp[i-w]+v);
}
void Complete_Pack(int w,int v){ 
    for(int i=w;i<=W;i++)
        dp[i]=max(dp[i],dp[i-w]+v);
}
int  Multi_Pack(){ 
    memset(dp,0,sizeof(dp));
    for(int i=0;i<n;i++){
        if(w[i]*c[i]>=W)Complete_Pack(w[i],v[i]);
        else {
            int k=1;
            while(k<c[i]){
                ZeroOne_Pack(w[i]*k,v[i]*k);
                c[i]-=k;
                k<<=1;
            }
            ZeroOne_Pack(w[i]*c[i],v[i]*c[i]);
        }
    }
    return dp[W];
}

3、多重部分和问题

题目:有n种不同大小的数字ai,每种各ci个,判断是否可以从这些数字之中选出若干使它们的和恰好为k。(1 ≤n≤100 , 1≤K≤100000)
思路: 定义dp[i+1][j]为前 i 种数加和得到 j 时第 i 种数最多能剩余多少(不能加和得到 i 的情况为 -1)

//多重部分和问题
int n,k;
int a[Max_n],c[Max_n];
int dp[Max_k];

bool solve(){
    memset(dp,-1,sizeof(dp));
    for(int i=0;i<n;i++){
        dp[0]=c[i];
        for(int j=1;j<=k;j++){ //递推关系
            if(dp[j]>=0)dp[j]=c[i];
            else if(j>=a[i]&&dp[j-a[i]]>0)dp[j]=dp[j-a[i]]-1;
            else dp[j]=-1;
        }
    }
    if(dp[k]>=0)return true;
    else return false;
}

4、划分数问题

题目:有n个无区别的物品,将它们划分成不超过m组,求出划分方法数模M的余数。(1 ≤m≤n≤1000)
思路: 考虑n的m划分ai(i=1,2,3…),如果对于每个ai>0,那么{ai-1}就对应了n-m的m划分,另外如果存在ai=0,那么就对应了n的m-1划分。定义dp[i][j]为 i 的 j 划分的总数,则dp[i][j]=dp[i][j-1]+dp[i-j][j]。

//划分数问题
int n,m;
int dp[Max_n][Max_m];

void solve(){
    memset(dp,0,sizeof(dp));
    dp[0][0]=1;
    for(int i=0;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(i>=j)dp[i][j]=(dp[i][j-1]+dp[i-j][j])%M;
            else dp[i][j]=dp[i][j-1];
        }
    }
    printf("%d\n",dp[n][m]);
}

5、多重集组合数问题

题目:有n种物品,第i种物品有ai个。不同种类的物品可以互相区分但相同种类的无法区分。从这些物品中取出m个的话,有多少种取法?求出方案数模M的余数。(1≤n≤1000,1≤m≤1000,1≤ai≤1000,2≤M≤10000)
思路: 定义dp[i][j]为从前i中物品中取出j个的组合总数。
① j≤a[i]时,dp[i][j]=dp[i-1][j]+dp[i-1][j] ② j>s[i]时,dp[i][j]=dp[i-1][j]+dp[i-1][j]-dp[i-1][j-1-a[i]]。

//多重集组合数问题
int n,m;
int dp[2][Max_n];

void solve(){
    memset(dp,0,sizeof(dp));
    dp[0][0]=dp[1][0]=1;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(j<=a[i])dp[i&1][j]=(dp[i&1][j-1]+dp[(i-1)&1][j])%mod;
            else dp[i&1][j]=(dp[i&1][j-1]+dp[(i-1)&1][j]-dp[(i-1)&1][j-1-a[i]]+mod)%mod;
            //在有取余的情况下,要避免减法运算的结果出现负数
        }
    }
    printf("%d\n",dp[n&1][m]);
}

猜你喜欢

转载自blog.csdn.net/zzti_xiaowei/article/details/79948278