C++:dp问题

1.鸣人的影分身

1050. 鸣人的影分身 - AcWing题库

在火影忍者的世界里,令敌人捉摸不透是非常关键的。

我们的主角漩涡鸣人所拥有的一个招数——多重影分身之术——就是一个很好的例子。

影分身是由鸣人身体的查克拉能量制造的,使用的查克拉越多,制造出的影分身越强。

针对不同的作战情况,鸣人可以选择制造出各种强度的影分身,有的用来佯攻,有的用来发起致命一击。

那么问题来了,假设鸣人的查克拉能量为 MM,他影分身的个数最多为 NN,那么制造影分身时有多少种不同的分配方法?

解题思路

上图为直接思考dp方式。f数组为一个当查克拉量为i时分配j个影分身有多少方法。

分为1.分配查克拉量最小的分身为0的情况 2.没有查克拉为0的分身的情况

还可以通过dfs的做法来推出dp的做法

AC代码
#include<iostream>
#include<algorithm>
#include<cstdio>

using namespace std;

int m,n;
int dp[25][1000];//i是查克拉总和,j分成的影分身,存储值为方案数
int  t;

int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&m,&n);
        
        dp[0][0]=1;//总和为0,0个分身,有1种方法
        for(int i=0;i<=m;i++)
        {
            for(int j=1;j<=n;j++)
            {
                dp[i][j]=dp[i][j-1];//这个总和有一个方案查克拉最少为0,分身数量减1就是没有0的情况
                if(i>=j)//没有方案查克拉为0的情况,其方案数与所有方案(j)都减去1(即i减去j)是一样的
                dp[i][j]+=dp[i-j][j];
            }
        }
        printf("%d\n",dp[m][n]);
        
    }
    
    return 0;
}

2. 糖果

1047. 糖果 - AcWing题库

由于在维护世界和平的事务中做出巨大贡献,Dzx被赠予糖果公司2010年5月23日当天无限量糖果免费优惠券。

在这一天,Dzx可以从糖果公司的 NN 件产品中任意选择若干件带回家享用。

糖果公司的 NN 件产品每件都包含数量不同的糖果。

Dzx希望他选择的产品包含的糖果总数是 KK 的整数倍,这样他才能平均地将糖果分给帮助他维护世界和平的伙伴们。

当然,在满足这一条件的基础上,糖果总数越多越好。

Dzx最多能带走多少糖果呢?

注意:Dzx只能将糖果公司的产品整件带走。

解题思路

数组存在前i个物品中选,总和除k的余数是j的糖果最大值。

不包含i的加上dp[i-1][j] ,j不需要改变,因为这个是不加上v[i]的情况,上一个也是没选i这个情况。

而包含i,j就必须减去v[i]后计算余数(j + k - v[i]%k) % k;保证余数非负与代码健壮性

示例如下

(3 - 7) % 5 = (-4) % 5 = 1

(3 + 5 - 2) % 5 = 6 % 5 = 1

再说明一下将dp数组进行复制为负数的原因

dp(0 ,i) (i != 1)都是不存在的状态,所以必须要初始化为负无穷,不然的话可以选择先初始化第一件物品的所有情况,再迭代第二个物品之后

AC代码
#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
//从前i个物品中选,且总和除以k的余数是j的所有方案
int dp[110][110];
int n,k;
int v[110];
int main()
{
    int n,k;
    scanf("%d%d",&n,&k);
    
    for(int i=1;i<=n;i++)
    scanf("%d",&v[i]);
    //dp(0 ,i) (i != 1)都是不存在的状态,所以必须要初始化为负无穷,
    //不然的话可以选择先初始化第一件物品的所有情况,再迭代第二个物品之后
    for (int i = 0; i < 110; ++i)
    for (int j = 0; j < 110; ++j)
        dp[i][j] =-1e8;
        
    dp[0][0]=0;
    
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<k;j++)
        {
            //不包含物品i与包含物品i中取一个较大值
            dp[i][j]=max(dp[i-1][j],dp[i-1][(j+k-v[i]%k)%k]+v[i]);
        }
    }
    printf("%d\n",dp[n][0]);
    return 0;
}

3. 密码脱落

1222. 密码脱落 - AcWing题库

X星球的考古学家发现了一批古代留下来的密码。

这些密码是由A、B、C、D 四种植物的种子串成的序列。

仔细分析发现,这些密码串当初应该是前后对称的(也就是我们说的镜像串)。

由于年代久远,其中许多种子脱落了,因而可能会失去镜像的特征。

你的任务是:

给定一个现在看到的密码串,计算一下从当初的状态,它要至少脱落多少个种子,才可能会变成现在的样子。

解题思路

上图的i j 与l r都指区间

区间dp存所有从i到j区间相同回文子序列的个数,注意四种情况,将前三种取最大值即可。并且在只有一个字符时初始化为1

AC代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
using namespace std;

//存储所有从i到j区间的相同字符长度
int dp[1010][1010];

int main()
{
    string s;
    cin>>s;
    int len=s.size();
    for(int i=1;i<=len;i++)//枚举区间长度
    {
        for(int l=0;l+i-1<len;l++)
        {
            int r=l+i-1;
            if(l==r)//是同一个字符
            {
                dp[l][r]=1;
            }
            else{
                //l与r位置字符相同,选两边
                if(s[l]==s[r])
                {
                    dp[l][r]=dp[l+1][r-1]+2;//上个区间增加两个字符相同
                }
                //假如l不同,看l-1与r比较现在的如何。
                if(dp[l][r] < dp[l+1][r])
                {
                    dp[l][r]=dp[l+1][r];
                }
                if( dp[l][r]<dp[l][r-1])//r不同,看r-1与l比现在的相同字符如何
                {
                    dp[l][r]=dp[l][r-1];
                }
            }
            
        }
    }
    // printf("%d\n",len);
    printf("%d",len-dp[0][len-1]);
    
    return 0;
}

4.生命之树

在X森林里,上帝创建了生命之树。

他给每棵树的每个节点(叶子也称为一个节点)上,都标了一个整数,代表这个点的和谐值。

上帝要在这棵树内选出一个非空节点集 SS,使得对于 SS 中的任意两个点 a,ba,b,都存在一个点列 {a,v1,v2,…,vk,b}{a,v1,v2,…,vk,b} 使得这个点列中的每个点都是 SS 里面的元素,且序列中相邻两个点间有一条边相连。

在这个前提下,上帝要使得 SS 中的点所对应的整数的和尽量大。

这个最大的和就是上帝给生命之树的评分。

经过 atm 的努力,他已经知道了上帝给每棵树上每个节点上的整数。

但是由于 atm 不擅长计算,他不知道怎样有效的求评分。

他需要你为他写一个程序来计算一棵树的分数。

解题思路

树状DP,就是遍历寻找最大的一个区域。建立一张图记录这个区域往下最大的权值为多少。

然后遍历这个数组找存储的最大值。

AC代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>

using namespace std;
const int N=1e5+10,M=2*N;
int n;
int w[N];//价值
int h[N],e[M],ne[M],idx;
// h是顶点集,e[i]=b表示a指向b(一条边),
//ne[i]表示结点i的next的指针,idx指向当前需要插入(已经用过)的结点
long long f[N];
void add(int a,int b)//a到b的边
{
    e[idx]=b;//记录边的终点为b
    ne[idx]=h[a];//下一个指针
    h[a]=idx++;//,h[a]代表所有以a为起点的单链表的头
}

int dfs(int u,int father)
{
    f[u]=w[u];//先记录自身节点
    for(int i=h[u];i!=-1;i=ne[i])
    {
        int j=e[i];//下一步
        if(j!=father)
        {
            dfs(j,u);
            f[u]+=max((long long)0,f[j]);//如果下一区域是负数宁愿不要
        }
    }
}

int main()
{
    scanf("%d",&n);
    
    memset(h,-1,sizeof h);
    for(int i=1;i<=n;i++)
    scanf("%d",&w[i]);
    
    for(int i=0;i<n-1;i++)
    {
        int a,b;
        scanf("%d %d",&a,&b);
        add(a,b),add(b,a);//无向图互相联通
    }
    dfs(1, -1);

    long long res = f[1];
    for (int i = 2; i <= n; i ++ ) res = max(res, f[i]);// 求f[1]到f[n]的max

    printf("%lld\n", res);
    
    return 0;
}

这篇就到这里了(๑′ᴗ‵๑)I Lᵒᵛᵉᵧₒᵤ❤

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值