线性dp(拆分篇)

11 篇文章 0 订阅
5 篇文章 0 订阅

先来一波题单(感觉真的ex)

呜呜呜学算法真的好难啊,真想变成魔法少女呢!
在这里插入图片描述

acwing 900 整数拆分
acwing 278 数字组合
acwing 279 自然数拆分
acwing 3428 放苹果
acwing 1050 鸣人的影分身
前三题总结看这里
注:数可以看作一些物品(在下说明中)
这些题目的特征就是将一堆数量为N的物品拆分成几堆数量不同的物品,在线性dp中大多数都是用物品去填背包的,所以拆分数稍微会难想一些,一般类似于数的拆分或者是分物品的题目可以划分为线性拆分类这种题目的类型与区间dp还是有些不同的,不知道为什么数的划分和区间的划分在初学时就感觉有部分不同,一般人的固定思维是将区间划分为两部分而面对数的划分的时候总感觉无从下手,因为大部分在面对数(数量的物品)的拆分的时候总是将数分成很多个数,在合并的时候不太好想,并且数的拆分总给人一种很乱的感觉,那么是因为新手面对集合的概念不是很清楚,在面对大集合去拆的时候不应该直接去想,而是应该逆过来想,将小的集合合并成一个大集合,这样就不会混乱了;

因为都是一样的套路所以我就不分析dp的式子了

900
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 1500;
const int mod = 1e9+7;
int f[N];
int main()
{
    int n;
    cin>>n;
    f[0]=1;
    for(int i=1;i<=n;i++)
    for(int j=i;j<=n;j++)
    f[j]=(f[j]+f[j-i])%mod;
    
    cout<<f[n];
    
    return 0;
}
278
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+100,M=2*1e4+100;
int f[N][M];
int a[N];
int n,m;

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>a[i],f[i][0]=1;
    f[0][0]=1;
    for(int i=1;i<=n;i++)
    {
        for(int j=m;j>=0;j--)
        {
            f[i][j]=max(f[i][j],f[i-1][j]);
            if(a[i]<=j)
            {
                f[i][j]+=f[i-1][j-a[i]];
            }
        }
    }
    cout<<f[n][m]<<endl;
    
    
    return 0;
}
#include<iostream>
#include<algorithm>
using namespace std;
int n,m;
int w[110];
int f[110],num=0;
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>w[i];
    f[0]=1;
    for(int i=1;i<=n;i++)
    {
        for(int j=m;j>=w[i];j--)
        {

            f[j]=f[j]+f[j-w[i]];
          
        }
    }

    cout<<f[m]<<endl;
    return 0;
}
279
//
// Created by YikN on 2022/5/2.
//

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
const int N = 2 * 1e5 + 100;
const int mod = 2147483648;
ll f[N];
int n;

/*
 *
 *自然数拆分 将每个数看作是一个背包,那么一个大的背包可以由两个小的背包组成
 * f[i][j]从前i个物品选,价值为j的背包
 * f[i][j]=f[i-1][j]+f[i-1][j-i]
 * 不选当前物品和选当前物品
 * f[i-1][j] + f[i-1][j-i];


 */



int main() {
    cin >> n;
    f[0] = 1;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++) {
            if (j >= i) {
                f[j] = (f[j] + f[j - i]) % mod;
            }
        }
    cout<<f[n] - 1 << endl;
    return 0;
}

放苹果和鸣人的这两题中拆分的表达式很是难想,第一因为在分的时候会考虑到空盘子,题目中指出可以有空盘子,但是在dp划分的时候总是会有一种感觉,感觉无从下手,因为不知道改分成几种情况,特别是还有0的情况,第二就是就算已经知道了没有0的情况,也很难想,因为这个没有0的集合不是靠两个子集合合并而来,反而更像是由自己的儿子增加了同盘子数的苹果
在dp思想中我觉得更像是等效和等价的,注意是等效的,就相当于3%2和5%2对于%2操作来说是等效的,对于数值结果来说是等价的,这是比较少见的,掌芝食了,那么在面对这些问题时最好的方法就是死记一个大集合是由一个小集合和一个数组成的,每次拆成这么两个东西即可。
看看博主的代码:

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 15;
int f[N][N];   //f[i][j]: 所有总和是i,分成j个数的方案数

int main()
{
    int m, n;
    while(cin >> m >> n)
    {
        f[0][0] = 1;
        for(int i = 0;i <= m;i ++)
        {
            for(int j = 1;j <= n;j ++)
            {
                f[i][j] = f[i][j-1];//在是0的情况下,将这种情况并入小集合
                if(i >= j)
                    f[i][j] += f[i-j][j];//非零情况将算等效集合计算
            }
        }
        printf("%d\n", f[m][n]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值