算法竞赛——动态规划详解

1.简单线性dp问题

题目1:01背包问题
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
在这里插入图片描述

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
const int N=1010;
int w[N],v[N];
int f[N][N];
int main(){
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
    for(int i=1;i<=n;i++){
        for(int j=0;j<=m;j++){//初始值取零取一取决于是否合法
            f[i][j]=f[i-1][j];//左半边的子集
            if(j>=v[i]) f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);//右半边的子集,有可能不存在
        }
    }
    cout<<f[n][m]<<endl;
    return 0;
}

题目2:地宫寻宝
X 国王有一个地宫宝库,是 n×m 个格子的矩阵,每个格子放一件宝贝,每个宝贝贴着价值标签。
宫的入口在左上角,出口在右下角。
小明被带到地宫的入口,国王要求他只能向右或向下行走。
走过某个格子时,如果那个格子中的宝贝价值比小明手中任意宝贝价值都大,小明就可以拿起它(当然,也可以不拿)。
当小明走到出口时,如果他手中的宝贝恰好是 k 件,则这些宝贝就可以送给小明。
请你帮小明算一算,在给定的局面下,他有多少种不同的行动方案能获得这 k 件宝贝。
输入格式
第一行 3 个整数,n,m,k,含义见题目描述。
接下来 n 行,每行有 m 个整数 Ci 用来描述宝库矩阵每个格子的宝贝价值。
输出格式
输出一个整数,表示正好取 k 个宝贝的行动方案数。
该数字可能很大,输出它对 1000000007 取模的结果。
数据范围
1≤n,m≤50,
1≤k≤12,
0≤Ci≤12
在这里插入图片描述

#include<iostream>
#include<string>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=55,MOD=1000000007;
int f[N][N][13][14];
int w[N][N];
int main(){
    int n,m,k;
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>w[i][j];
            w[i][j]++;
        }
    }
    f[1][1][1][w[1][1]] = 1;
    f[1][1][0][0] = 1;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(i==1&&j==1) continue;
            for(int u=0;u<=k;u++){
                for(int v=0;v<=13;v++){
                    int &val = f[i][j][u][v];
                    val=(val+f[i-1][j][u][v])%MOD;
                    val=(val+f[i][j-1][u][v])%MOD;
                    if(u>0&&v==w[i][j]){
                        for(int c=0;c<v;c++){
                            val=(val+f[i-1][j][u-1][c])%MOD;
                            val=(val+f[i][j-1][u-1][c])%MOD;
                        }
                    }
                }
            }
        }
    }
    int res=0;
    for (int i = 0; i <= 13; i ++ ) res = (res + f[n][m][k][i]) % MOD;
    cout<<res;
    return 0;
}

题目三:波动数列
观察这个数列:
1 3 0 2 -1 1 -2 …
这个数列中后一项总是比前一项增加2或者减少3,且每一项都为整数。
栋栋对这种数列很好奇,他想知道长度为 n 和为 s 而且后一项总是比前一项增加 a 或者减少 b 的整数数列可能有多少种呢?
输入格式
共一行,包含四个整数 n,s,a,b,含义如前面所述。
输出格式
共一行,包含一个整数,表示满足条件的方案数。
由于这个数很大,请输出方案数除以 100000007 的余数。
数据范围
1≤n≤1000,
−109≤s≤109,
1≤a,b≤106
在这里插入图片描述

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=1010,MOD = 100000007;
int f[N][N];
int get_mod(int a,int b){
    return (a%b+b)%b;
}
int main(){
    int n,s,a,b;
    cin>>n>>s>>a>>b;
    f[0][0]=1;
    for(int i=1;i<n;i++){
        for(int j=0;j<n;j++){
            f[i][j]=(f[i-1][get_mod(j-a*(n - i),n)]+f[i-1][get_mod(j+(n - i)*b,n)])%MOD;
        }
    }
    cout<<f[n-1][get_mod(s,n)];
    return 0;
}

2、完全背包问题

题目1:
有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。
第 i 种物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 种物品的体积和价值。
输出格式
输出一个整数,表示最大价值。

#include<cstdio>
#include<algorithm>
using namespace std;
const int N=1010;
int f[N][N],v[N],w[N];
int n,V;
int main(){
    scanf("%d%d",&n,&V);
    for(int i=1;i<=n;i++){
        scanf("%d%d",&v[i],&w[i]);
    }
    f[0][0]=0;
    for(int i=1;i<=n;i++){
        for(int j=0;j<=V;j++){
            // for(int k=0;k*v[i]<=j;k++){
            //     f[i][j]=max(f[i-1][j],f[i-1][j-k*v[i]]+k*w[i]);
            // }这样写会爆超时,我们要进行优化;
            f[i][j]=f[i-1][j];
            if(j>=v[i]) f[i][j]=max(f[i][j-v[i]]+w[i],f[i-1][j]);
        }
    }
    printf("%d",f[n][V]);
}

题目2:包子凑数
小明几乎每天早晨都会在一家包子铺吃早餐。
他发现这家包子铺有 N 种蒸笼,其中第 i 种蒸笼恰好能放 Ai 个包子。
每种蒸笼都有非常多笼,可以认为是无限笼。
每当有顾客想买 X 个包子,卖包子的大叔就会迅速选出若干笼包子来,使得这若干笼中恰好一共有 X 个包子。
比如一共有 3 种蒸笼,分别能放 3、4 和 5 个包子。
当顾客想买 11 个包子时,大叔就会选 2 笼 3 个的再加 1 笼 5 个的(也可能选出 1 笼 3 个的再加 2 笼 4 个的)。
当然有时包子大叔无论如何也凑不出顾客想买的数量。
比如一共有 3 种蒸笼,分别能放 4、5 和 6 个包子。
而顾客想买 7 个包子时,大叔就凑不出来了。
小明想知道一共有多少种数目是包子大叔凑不出来的。
输入格式
第一行包含一个整数 N。
接下来 N 行,每行包含一个整数 Ai。
输出格式
输出一个整数代表答案。
如果凑不出的数目有无限多个,输出INF。
数据范围
1≤N≤100,
1≤Ai≤100,

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=110;
const int maxd=10010;
int w[N];
bool f[N][maxd];//
int n;
int gcd(int a, int b)
{
    return b ? gcd(b, a % b) : a;
}
int main(){
    scanf("%d",&n);
    int d=0;//代表最大公约数;
    for(int i=1;i<=n;i++){
        scanf("%d",&w[i]);
        d=gcd(d,w[i]);
    }
    if(d!=1)puts("INF");
    else {
        f[0][0]=true;
        for(int i=1;i<=n;i++){
            for(int j=0;j<=maxd;j++){
                f[i][j]=f[i-1][j];
                if(j>=w[i]) f[i][j]|=f[i][j-w[i]];
            }
        }
        int res=0;
        for(int i=0;i<maxd;i++){
            if(!f[n][i]) res++;
        }
        printf("%d\n",res);
    }
    return 0;
}

3、 区间dp

题目1:密码脱落
X星球的考古学家发现了一批古代留下来的密码。
这些密码是由A、B、C、D 四种植物的种子串成的序列。
仔细分析发现,这些密码串当初应该是前后对称的(也就是我们说的镜像串)。
由于年代久远,其中许多种子脱落了,因而可能会失去镜像的特征。
你的任务是:
给定一个现在看到的密码串,计算一下从当初的状态,它要至少脱落多少个种子,才可能会变成现在的样子。
输入格式
共一行,包含一个由大写字母ABCD构成的字符串,表示现在看到的密码串。
输出格式
输出一个整数,表示至少脱落了多少个种子。
数据范围
输入字符串长度不超过1000

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=1010;
char s[N];
int f[N][N];//表示集合的最大子序列(回文串);
int main(){
    scanf("%s",s);
    int n=strlen(s);//区间循环,先循环区间长度,再循环左,算出右
    for(int len=1;len<=N;len++){
        for(int l=0;l+len-1<n;l++){
            int r=l+len-1;
            if(len==1) f[l][r]=1;
            else{
                if(s[l]==s[r]) f[l][r]=f[l+1][r-1]+2;
                if(f[l+1][r]>f[l][r]) f[l][r]=f[l+1][r];
                if(f[l][r-1]>f[l][r]) f[l][r]=f[l][r-1];
            }
        }
    }
    printf("%d", n-f[0][n-1]);
    return 0;
}

题目二:括号配对
Hecy 又接了个新任务:BE 处理。
BE 中有一类被称为 GBE。
以下是 GBE 的定义:
空表达式是 GBE
如果表达式 A 是 GBE,则 [A] 与 (A) 都是 GBE
如果 A 与 B 都是 GBE,那么 AB 是 GBE
下面给出一个 BE,求至少添加多少字符能使这个 BE 成为 GBE。
注意:BE 是一个仅由(、)、[、]四种字符中的若干种构成的字符串。
输入格式
输入仅一行,为字符串 BE。
输出格式
输出仅一个整数,表示增加的最少字符数。
数据范围
对于所有输入字符串,其长度小于100。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
const int N=110;
int f[N][N];
const int INF=1e9;
string s;
bool is_match(char a,char b){
    if(a=='('&&b==')') return true;
    if(a=='['&&b==']')  return true;
    return false;
}
int main(){
    cin>>s;
    int n=s.size();//字符串的长度;
    for(int len=1;len<=n;len++){
        for(int i=0;i+len-1<n;i++){
            int j=i+len-1;
            f[i][j]=INF;
            if(is_match(s[i],s[j])) f[i][j]=f[i+1][j-1];
            if(j>=1) f[i][j]=min(f[i][j],min(f[i+1][j],f[i][j-1])+1);
            for(int k=i;k<j;k++){
                f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]);
            }
        }
    }
    cout<<f[0][n-1];
    
    return 0;
}

4、树形dp问题

题目1:生命之树
题目2:旅游规划

5、矩阵,快速幂优化的dp问题

题目1:斐波那契数列
大家都知道 Fibonacci 数列吧,f1=1,f2=1,f3=2,f4=3,…,fn=fn−1+fn−2。
现在问题很简单,输入 n 和 m,求 fn 的前 n 项和 Snmodm。
输入格式
共一行,包含两个整数 n 和 m。
输出格式
输出前 n 项和 Snmodm 的值。
数据范围
1≤n≤2000000000,
1≤m≤1000000010

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 3;
int n, m;
void mul(int c[], int a[], int b[][N])
{
    int temp[N] = {0};
    for (int i = 0; i < N; i ++ )
        for (int j = 0; j < N; j ++ )
            temp[i] = (temp[i] + (LL)a[j] * b[j][i]) % m;
    memcpy(c, temp, sizeof temp);
}

void mul(int c[][N], int a[][N], int b[][N])
{
    int temp[N][N] = {0};
    for (int i = 0; i < N; i ++ )
        for (int j = 0; j < N; j ++ )
            for (int k = 0; k < N; k ++ )
                temp[i][j] = (temp[i][j] + (LL)a[i][k] * b[k][j]) % m;
    memcpy(c, temp, sizeof temp);
}
int main()
{
    cin >> n >> m;
    int f1[N] = {1, 1, 1};
    int a[N][N] = {
        {0, 1, 0},
        {1, 1, 1},
        {0, 0, 1}
    };
    n -- ;
    while (n)
    {
        if (n & 1) mul(f1, f1, a);  // res = res * a
        mul(a, a, a);  // a = a * a
        n >>= 1;
    }
    cout << f1[2] << endl;
    return 0;
}

题目二:垒骰子
赌圣atm晚年迷恋上了垒骰子,就是把骰子一个垒在另一个上边,不能歪歪扭扭,要垒成方柱体。
经过长期观察,atm 发现了稳定骰子的奥秘:有些数字的面贴着会互相排斥!
我们先来规范一下骰子:1 的对面是 4,2 的对面是 5,3 的对面是 6。
假设有 m 组互斥现象,每组中的那两个数字的面紧贴在一起,骰子就不能稳定的垒起来。
atm想计算一下有多少种不同的可能的垒骰子方式。
两种垒骰子方式相同,当且仅当这两种方式中对应高度的骰子的对应数字的朝向都相同。
由于方案数可能过多,请输出模 109+7 的结果。
输入格式
第一行包含两个整数 n,m,分别表示骰子的数目和排斥的组数。
接下来 m 行,每行两个整数 a,b,表示 a 和 b 数字不能紧贴在一起。
输出格式
共一个数,表示答案模 109+7 的结果。
数据范围
1≤n≤109,
1≤m≤36,
1≤a,b≤6

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=6,mod=1e9+7;
typedef long long LL;
int a[N][N];//快速幂的矩阵
int n,m;
int  get_opp(int x){
    if(x>=3) return x-3;
    return x+3;
}
void mul(int c[][N],int a[][N],int b[][N]){
    int temp[N][N];
    memset(temp, 0, sizeof temp);
    for(int i=0;i<N;i++){
        for(int j=0;j<N;j++){
            for(int k=0;k<N;k++){
                temp[i][j]=(temp[i][j]+(LL)a[i][k]*b[k][j])%mod;
            }
        }
    }
    memcpy(c,temp,sizeof temp);
}
int main(){
    cin>>n>>m;
    for(int i=0;i<N;i++){
        for(int j=0;j<N;j++){
            a[i][j]=4;
        }
    }
    while(m--){
        int x,y;
        scanf("%d%d",&x,&y);
        x--,y--;
        a[x][get_opp(y)]=0;
        a[y][get_opp(x)]=0;//将限制条件加到快速幂矩阵当中去;
    }
    n--;
    int f[N][N]={4,4,4,4,4,4};//将一维的数组拓展成为二维的数组,便于计算;
    while(n){
        if(n&1) mul(f,f,a);//f=f*a;
        mul(a,a,a);//a=a*a;
        n>>=1;//n=n/2;
    }
    int res=0;
    for(int i=0;i<N;i++) res=(res+f[0][i])%mod;
    printf("%d\n",res);
    return 0;
}
拍贷“魔镜风控系统”从平均 拍贷“魔镜风控系统”从平均 拍贷“魔镜风控系统”从平均 拍贷“魔镜风控系统”从平均 拍贷“魔镜风控系统”从平均 拍贷“魔镜风控系统”从平均 拍贷“魔镜风控系统”从平均 拍贷“魔镜风控系统”从平均 400 个数据维度评估用户当前的信状态,给每借款 个数据维度评估用户当前的信状态,给每借款 个数据维度评估用户当前的信状态,给每借款 个数据维度评估用户当前的信状态,给每借款 个数据维度评估用户当前的信状态,给每借款 个数据维度评估用户当前的信状态,给每借款 个数据维度评估用户当前的信状态,给每借款 个数据维度评估用户当前的信状态,给每借款 个数据维度评估用户当前的信状态,给每借款 个数据维度评估用户当前的信状态,给每借款 人打出当前状态的 信用分,在此基础上再结合新发标息对于每个人打出当前状态的 信用分,在此基础上再结合新发标息对于每个人打出当前状态的 信用分,在此基础上再结合新发标息对于每个人打出当前状态的 信用分,在此基础上再结合新发标息对于每个人打出当前状态的 信用分,在此基础上再结合新发标息对于每个人打出当前状态的 信用分,在此基础上再结合新发标息对于每个人打出当前状态的 信用分,在此基础上再结合新发标息对于每个人打出当前状态的 信用分,在此基础上再结合新发标息对于每个人打出当前状态的 信用分,在此基础上再结合新发标息对于每个人打出当前状态的 信用分,在此基础上再结合新发标息对于每个人打出当前状态的 信用分,在此基础上再结合新发标息对于每个人打出当前状态的 信用分,在此基础上再结合新发标息对于每个人打出当前状态的 信用分,在此基础上再结合新发标息对于每个人打出当前状态的 信用分,在此基础上再结合新发标息对于每个人打出当前状态的 信用分,在此基础上再结合新发标息对于每个人打出当前状态的 信用分,在此基础上再结合新发标息对于每个人打出当前状态的 信用分,在此基础上再结合新发标息对于每个6个月内逾 个月内逾 期率的预测 ,为投资人提供关键决策依据。本次竞赛目标是根用户历史行数来期率的预测 ,为投资人提供关键决策依据。本次竞赛目标是根用户历史行数来期率的预测 ,为投资人提供关键决策依据。本次竞赛目标是根用户历史行数来期率的预测 ,为投资人提供关键决策依据。本次竞赛目标是根用户历史行数来期率的预测 ,为投资人提供关键决策依据。本次竞赛目标是根用户历史行数来期率的预测 ,为投资人提供关键决策依据。本次竞赛目标是根用户历史行数来期率的预测 ,为投资人提供关键决策依据。本次竞赛目标是根用户历史行数来期率的预测 ,为投资人提供关键决策依据。本次竞赛目标是根用户历史行数来期率的预测 ,为投资人提供关键决策依据。本次竞赛目标是根用户历史行数来期率的预测 ,为投资人提供关键决策依据。本次竞赛目标是根用户历史行数来期率的预测 ,为投资人提供关键决策依据。本次竞赛目标是根用户历史行数来期率的预测 ,为投资人提供关键决策依据。本次竞赛目标是根用户历史行数来期率的预测 ,为投资人提供关键决策依据。本次竞赛目标是根用户历史行数来期率的预测 ,为投资人提供关键决策依据。本次竞赛目标是根用户历史行数来期率的预测 ,为投资人提供关键决策依据。本次竞赛目标是根用户历史行数来期率的预测 ,为投资人提供关键决策依据。本次竞赛目标是根用户历史行数来期率的预测 ,为投资人提供关键决策依据。本次竞赛目标是根用户历史行数来期率的预测 ,为投资人提供关键决策依据。本次竞赛目标是根用户历史行数来期率的预测 ,为投资人提供关键决策依据。本次竞赛目标是根用户历史行数来用户在未来 用户在未来 用户在未来 6个月内是否会逾期还款的概率。 个月内是否会逾期还款的概率。 个月内是否会逾期还款的概率。 个月内是否会逾期还款的概率。 个月内是否会逾期还款的概率。 个月内是否会逾期还款的概率。 个月内是否会逾期还款的概率。 问题转换成 问题转换成 问题转换成 2分类问题,评估指标为 分类问题,评估指标为 分类问题,评估指标为 分类问题,评估指标为 分类问题,评估指标为 分类问题,评估指标为 分类问题,评估指标为 AUC ,从 Master Master Master,LogInfoLogInfo LogInfo ,UpdateInfo UpdateInfo UpdateInfo 表中构建 表中构建 特征,考虑评估指标为 特征,考虑评估指标为 特征,考虑评估指标为 特征,考虑评估指标为 特征,考虑评估指标为 AUC AUC,其本质是排序优化问题,所以我们在模型顶层融合也使用基于 ,其本质是排序优化问题所以我们在模型顶层融合也使用基于 ,其本质是排序优化问题所以我们在模型顶层融合也使用基于 ,其本质是排序优化问题所以我们在模型顶层融合也使用基于 ,其本质是排序优化问题所以我们在模型顶层融合也使用基于 ,其本质是排序优化问题所以我们在模型顶层融合也使用基于 ,其本质是排序优化问题所以我们在模型顶层融合也使用基于 ,其本质是排序优化问题所以我们在模型顶层融合也使用基于 ,其本质是排序优化问题所以我们在模型顶层融合也使用基于 ,其本质是排序优化问题所以我们在模型顶层融合也使用基于 ,其本质是排序优化问题所以我们在模型顶层融合也使用基于 ,其本质是排序优化问题所以我们在模型顶层融合也使用基于 ,其本质是排序优化问题所以我们在模型顶层融合也使用基于 ,其本质是排序优化问题所以我们在模型顶层融合也使用基于 ,其本质是排序优化问题所以我们在模型顶层融合也使用基于 排序优化的 排序优化的 排序优化的 RANK_AVG RANK_AVG RANK_AVG融合方法。 融合方法。 融
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值