【缄*默】 #DP专题# 缓缓推进的DP练习...

基本是DP的练习题,不会太难qwq

【T1】洛谷 P1164 小A点菜

  • 有M(1<=M<=10000)元钱,有N(1<=N<=100)种菜,
  • 第i种卖a[i]元(1<=a[i]<=10000),请你求出花完M元的点菜方法。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

/*【T1】洛谷 P1164 小A点菜
有M(1<=M<=10000)元钱,有N(1<=N<=100)种菜,
第i种卖a[i]元(1<=a[i]<=10000),请你求出花完M元的点菜方法。*/

/*【分析】设f[j]表示剩余j元时的最大方案数。
f[0] = 1 -> 没钱的时候什么都不买,也算是一种方案。
递归式:f[j] = f[j-cost[i]]。*/

int n,m,cost,f[1001];

int main(){
    scanf("%d%d",&n,&m);
    memset(f,0,sizeof(f)); //DP数组清零
    f[0] = 1; //设置DP起点
    for(int i=1;i<=n;i++){
        scanf("%d",&cost);
        for(int j=m;j>=cost;j--){
            f[j]+=f[j-cost]; //DP方程
        }
    }
    cout<<f[m]<<endl;
    return 0;
}

【T2】caioj 1411 打牌

  • 应该有n张牌,但少了几张,已知牌的总重量tw,
  • 和每张牌的重量wi,把缺少的牌找出来。
  • 如果无解,输出“0”,如果多解,输出“-1”。
  • 否则按照升序输出缺少牌的编号(相邻两个数用空格隔开)。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

/*【T2】caioj 1411 打牌
应该有n张牌,但少了几张,已知牌的总重量tw,
和每张牌的重量wi,把缺少的牌找出来。
如果无解,输出“0”,如果多解,输出“-1”。
否则按照升序输出缺少牌的编号(相邻两个数用空格隔开)。 */

/*【分析】f[j]表示前i张牌的形成重量j的方案数。
1.记录每张牌对方案数f[j]的改变。方案数的求法:f[j]+=f[j-a[i]];
  f[j-a[i]]表示当前牌出现之前,形成重量j-a[i]的方案数。
  f[0]=1,即重量为0的方案数初始化为1。
2.用c[j]记录重量j会用到的牌的序号
3.输出时根据f[w]的值判断,用v表示重量,从w倒推,
  用ans[c[v]]表示第c[v]张牌是否出现,
  因为之前在输入中用c[v]来表示重量v中一定会出现的牌的最大序号。
  v-=a[c[v]]表示除去第c[v]张牌后的剩余重量,直到为0。
  然后顺序判断ans[i],若为0,则表示第i张牌未出现,输出i即可。*/

int w,n,f[110002]={1}; //f数组初始化为1
int c[110002],ans[102],a[102];

int main(){
    cin>>w>>n; //总重+牌数
    for(int i=1;i<=n;i++){
        cin>>a[i];
        for(int j=w;j>=a[i];j--)
            if(f[j-a[i]]){
                f[j]+=f[j-a[i]];//不同重量带来的方案数
                if(!c[j]) c[j]=i; //重量j会用到的牌的序号
                //这里只用记录凑到这种情况的第一个序号
                //因为减了它之后,余下的能提供其他的牌的序号
            }
    }  
    if(!f[w]) cout<<"0\n";
    if(f[w]>1) cout<<"-1\n"; //方案数>1
    if(f[w]==1){
        int v=w; while(v) ans[c[v]]=1,v-=a[c[v]];
        for(int i=1;i<=n;i++)
            if(!ans[i]) cout<<i<<' ';
        cout<<endl;
    }
    return 0;
}

【T3】洛谷 P1832 A+B problem

  • 给定一个正整数n,求将其分解成若干个素数之和的方案总数。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

/*【T3】洛谷 P1832 A+B problem(再升级)
给定一个正整数n,求将其分解成若干个素数之和的方案总数。 */

/*【分析】无限背包dp。状态转移求方案数。
把1~n的所有素数看做是物品,每个素数可以无限放,注意f[0]=1;
F[j]+=F[j-a[i]],把素数a[i]放入背包后,增加方案数。
筛法求出素数,累加质数的状态之和。 */

bool mark[1005];
ll dp[1500],prime[1005],n,num;

int main(){
    scanf("%d",&n);
    for(int i=2;i<=n;i++){ //素数表
        if(mark[i]==false) prime[num++]=i; //记录素数
        for(int j=0;j<num&&prime[j]*i<=n;j++){
            mark[prime[j]*i]=true; //↑↑线性筛法
            if(i%prime[j]==0) break;
        }
    }
    dp[0]=1;
    for(int i=0;i<num;i++)
        for(int j=prime[i];j<=n;j++)
            dp[j]+=dp[j-prime[i]]; //方案数累加
    printf("%lld",dp[n]);
    return 0;
}

【T4】洛谷 P2285 打鼹鼠

  • 在一个N*N(1<=n<=1000)的网格中,控制一个机器人打鼹鼠,
  • 如果[i时刻]鼹鼠和机器人处于同一网格,就可以打死。
  • 机器人每一时刻只能向上下左右四个网格移动一格,或原地不动。
  • 游戏开始时,你可以自由选定机器人的初始位置。
  • 现在你知道在一段时间内有M(1<=M<=10000)个鼹鼠和它出现的时间T和地点(X,Y),
  • 要求你求出机器人最多打死的鼹鼠数量。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

/*【T4】洛谷 P2285 打鼹鼠
在一个N*N(1<=n<=1000)的网格中,控制一个机器人打鼹鼠,
如果[i时刻]鼹鼠和机器人处于同一网格,就可以打死。
机器人每一时刻只能向上下左右四个网格移动一格,或原地不动。
游戏开始时,你可以自由选定机器人的初始位置。
现在你知道在一段时间内有M(1<=M<=10000)个鼹鼠和它出现的时间T和地点(X,Y),
要求你求出机器人最多打死的鼹鼠数量。*/

/*【分析】不需要清晰地知道某一个时刻机器人的位置以及机器人的走法,
只用关心某个时间点,机器人能不能打死那个位置上的鼹鼠。
用f[i]表示打完前i个鼹鼠、并且打死第i个鼹鼠时,打死的最多数量。
那么对于之前的某个f[j],如果j鼹鼠和i鼹鼠的距离在足够时间单位内,
那么f[i]可以从f[j]转移过来。*/

const int N=10005;
int n,m,t[N],x[N],y[N],f[N],ans=0;

int dis(int i,int j){ return abs(x[i]-x[j])+abs(y[i]-y[j]); }

int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++) 
        scanf("%d%d%d",&t[i],&x[i],&y[i]);
    for(int i=1;i<=m;i++){
        f[i]=1; //初始化:最初的时刻肯定可以打死
        for(int j=1;j<i;j++)
            if(dis(i,j)<=(t[i]-t[j])) 
                f[i]=max(f[i],f[j]+1);
        ans=max(ans,f[i]);
    }
    printf("%d\n",ans);
    return 0;
}

【T5】洛谷 P1006 传纸条

  • 给出M*N(1<=M,N<=50)的矩阵,在这个矩阵内找出两条从1,1到m,n的路径。
  • (一条从1,1到m,n;一条从m,n到1,1),且路径之上的权值和k最大。
  • PS:(1,1)和(m,n)权值为0。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

/*【T5】洛谷 P1006 传纸条
给出M*N(1<=M,N<=50)的矩阵,在这个矩阵内找出两条从1,1到m,n的路径。
(一条从1,1到m,n;一条从m,n到1,1),且路径之上的权值和k最大。
PS:(1,1)和(m,n)权值为0。 */

const int maxn=60;
int a[maxn][maxn];

int F[2*maxn][maxn][maxn]; 
/* 第一维度维护的是纵坐标与横坐标的和。
   =>在同一斜线上 =>结合纵坐标就可以找到确切位置。
   第二维度维护的是相对在左边的点的纵坐标。
   第三维度维护的是相对在右边的点的纵坐标。*/

//F[sum][i][j]=max{F[sum-1][i][j],F[sum-1][i][j-1],F[sum-1][i-1][j],F[sum-1][i-1][j-1]};

int main(){
    int m,n; scanf("%d%d",&m,&n);
    for(int i=1;i<=m;i++)
        for(int j=1;j<=n;j++) scanf("%d",&a[i][j]);
    memset(F,-1,sizeof(F));//赋初值为-1 
    F[2][1][1]=0; //最初的点,在左上角,好感度为0 [初始化]
    for(int k=3;k<m+n;k++) //纵坐标与横坐标的和
        for(int i=1;i<n;i++)
            for(int j=i+1;j<=n;j++){
                int s=F[k][i][j]; //讨论各种情况,更不更新
                if(F[k-1][i][j]>s) s=F[k-1][i][j];
                if(F[k-1][i-1][j]>s) s=F[k-1][i-1][j];
                if(F[k-1][i][j-1]>s) s=F[k-1][i][j-1];
                if(F[k-1][i-1][j-1]>s) s=F[k-1][i-1][j-1];
                if(s==-1) continue; 
                //↑↑↑当s为-1时,说明四种情况都不能到该点,故不存在
                F[k][i][j]=s+a[k-i][i]+a[k-j][j];
                //↑↑↑该点的值为最大的前一个值与当前F[k][i][j]表示两点的值的和
            }
    printf("%d",F[m+n-1][n-1][n]); //因为i永远小于j,所以右下角的点不会求到
    //但是到右下角只有一种情况,在右下角的上面和右下角的左边,直接输出即可
    return 0;
} 
扫描二维码关注公众号,回复: 2977588 查看本文章

                                               ——时间划过风的轨迹,那个少年,还在等你。

猜你喜欢

转载自blog.csdn.net/flora715/article/details/81905728