数学与简单动态规划

买不到的数目

题目大意
小明开了一家糖果店。
他别出心裁:把水果糖包成4颗一包和7颗一包的两种。糖果不能拆包卖。
小朋友来买糖的时候,他就用这两种包装来组合。
当然有些糖果数目是无法组合出来的,比如要买 10 颗糖。
你可以用计算机测试一下,在这种包装情况下,最大不能买到的数量是17。
大于17的任何数字都可以用4和7组合出来。
本题的要求就是在已知两个包装的数量时,求最大不能组合出的数字。
输入格式
两个正整数 n,m,表示每种包装中糖的颗数。
输出格式
一个正整数,表示最大不能买到的糖数。
数据范围
2≤n,m≤1000 保证数据一定有解。
输入样例

4 7

输出样例

17

解题思路
两个正整数n , m互质,它们最大的不能凑出来的正整数是(n-1)(m-1)-1

#include <cstdio>
using namespace std;
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    printf("%d\n",(n-1)*(m-1)-1);
    return 0;
}

蚂蚁感冒

题目大意
长 100 厘米的细长直杆子上有 n 只蚂蚁。它们的头有的朝左,有的朝右。 每只蚂蚁都只能沿着杆子向前爬,速度是 1 厘米/秒。当两只蚂蚁碰面时,它们会同时掉头往相反的方向爬行。这些蚂蚁中,有 1 只蚂蚁感冒了。并且在和其它蚂蚁碰面时,会把感冒传染给碰到的蚂蚁。请你计算,当所有蚂蚁都爬离杆子时,有多少只蚂蚁患上了感冒。
输入格式
第一行输入一个整数 n, 表示蚂蚁的总数。
接着的一行是 n 个用空格分开的整数 X i _i i, X i _i i的绝对值表示蚂蚁离开杆子左边端点的距离。
正值表示头朝右,负值表示头朝左,数据中不会出现 0 值,也不会出现两只蚂蚁占用同一位置。其中,第一个数据代表的蚂蚁感冒了。
输出格式
输出1个整数,表示最后感冒蚂蚁的数目。
数据范围
1<n<50,0<|X i _i i|<100
输入样例

5
-10 8 -20 12 25

输出样例

3

解题思路
第一个蚂蚁向右走的情况:
1.右边向左走,必然被感染。
2.右边向右走,必然不会被感染
3.左边向左走,必然不会被感染
4.左边向右走:
(1)右边存在向左走,则必然被感染。
(2)右边不存在向左走,则必然不会被感染

#include <cstdio>
#include <iostream>
#include <cmath>
using namespace std;
int main()
{
    int n,l=0,r=0,x,t;
    scanf("%d%d",&n,&x);
    for(int i=1;i<n;++i){
        scanf("%d",&t);
        if(abs(t)<abs(x)&&t>0) l++;   //左边位置向右走
        if(abs(t)>abs(x)&&t<0) r++;   //右边位置向左走
    }
    if((!l&&x<0)||(!r&&x>0)) printf("1\n");  //未感染
    else printf("%d\n",l+r+1);
    return 0;
}

饮料换购

题目大意
乐羊羊饮料厂正在举办一次促销优惠活动。乐羊羊C型饮料,凭3个瓶盖可以再换一瓶C型饮料,并且可以一直循环下去(但不允许暂借或赊账)。
请你计算一下,如果小明不浪费瓶盖,尽量地参加活动,那么,对于他初始买入的 n 瓶饮料,最后他一共能喝到多少瓶饮料。
输入格式
输入一个整数 n,表示初始买入的饮料数量。
输出格式
输出一个整数,表示一共能够喝到的饮料数量。
数据范围:0<n<10000
输入样例

100

输出样例

149

解题思路
三个瓶盖换一瓶饮料,如果瓶盖数 < 3的时候就不能换饮料,不断更新瓶盖数量,最后输出结果。

#include <iostream>
using namespace std;
int main()
{
    int n,cnt=0;
    cin>>n;
    cnt=n;
    while(n>=3)
    {
        int t=n%3;
        cnt+=n/3;
        n/=3;
        n+=t;
    }
    cout<<cnt<<endl;
    return 0;
}

01背包问题

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

4 5
1 2
2 4
3 4
4 5

输出样例

8

二维的做法:

for(int i=1;i<=n;++i)
    for(int j=v;j>=0;--j)
        if(j>=V[i]) dp[i][j]=max(dp[i-1][j],dp[i-1][j-V[i]]+W[i]);
        else dp[i][j]=dp[i-1][j];
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=1e3+7;
int dp[maxn][maxn];   //表示体积为 v的背包能装价值为w的物品
int V[maxn],W[maxn];
int main()
{
    int n,v;
    scanf("%d%d",&n,&v);
    for(int i=1;i<=n;++i) scanf("%d%d",&V[i],&W[i]);
    
    for(int i=1;i<=n;++i)
        for(int j=v;j>=0;--j)
            if(j>=V[i]) dp[i][j]=max(dp[i-1][j],dp[i-1][j-V[i]]+W[i]);
            else dp[i][j]=dp[i-1][j];
        
    printf("%d\n",dp[n][v]);
    return 0;
}

一维的做法:

for(int i=1;i<=n;++i)
    for(int j=v;j>=V[i];--j)
        if(dp[j-V[i]]+W[i]>dp[j]) dp[j]=dp[j-V[i]]+W[i];
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=1e3+7;
int dp[maxn];   
int V[maxn],W[maxn];
int main()
{
    int n,v;
    scanf("%d%d",&n,&v);
    for(int i=1;i<=n;++i) scanf("%d%d",&V[i],&W[i]);
    
    for(int i=1;i<=n;++i)
        for(int j=v;j>=V[i];--j)
            if(dp[j-V[i]]+W[i]>dp[j]) dp[j]=dp[j-V[i]]+W[i];
        
    printf("%d\n",dp[v]);
    return 0;
}

摘花生

题目大意
Hello Kitty想摘点花生送给她喜欢的米老鼠。她来到一片有网格状道路的矩形花生地(如下图),从西北角进去,东南角出来。地里每个道路的交叉点上都有种着一株花生苗,上面有若干颗花生,经过一株花生苗就能摘走该它上面所有的花生。Hello Kitty只能向东或向南走,不能向西或向北走。问Hello Kitty最多能够摘到多少颗花生。
在这里插入图片描述
输入格式
第一行是一个整数T,代表一共有多少组数据。
接下来是T组数据。
每组数据的第一行是两个整数,分别代表花生苗的行数R和列数 C。
每组数据的接下来R行数据,从北向南依次描述每行花生苗的情况。每行数据有C个整数,按从西向东的顺序描述了该行每株花生苗上的花生数目M。
输出格式
对每组输入数据,输出一行,内容为Hello Kitty能摘到得最多的花生颗数。
数据范围 1≤T≤100,1≤R,C≤100,0≤M≤1000
输入样例

2
2 2
1 1
3 4
2 3
2 3 4
1 6 5

输出样例

8
16

解题思路
dp[i][j]:表示第 i 行第 j 列的花生数量。
状态转移方程: dp[i][j]+=max(dp[i-1][j],dp[i][j-1])

#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e2+7;
ll dp[maxn][maxn];
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int r,c;
        scanf("%d%d",&r,&c);
        for(int i=1;i<=r;++i)
            for(int j=1;j<=c;++j) scanf("%lld",&dp[i][j]);
        
        for(int i=1;i<=r;++i)
            for(int j=1;j<=c;++j)
                dp[i][j]+=max(dp[i-1][j],dp[i][j-1]);
        printf("%d\n",dp[r][c]);
    }
    return 0;
}

最长上升子序列

题目大意
给定一个长度为N的数列,求数值严格单调递增的子序列的长度最长是多少。
输入格式
第一行包含整数N。
第二行包含N个整数,表示完整序列。
输出格式
输出一个整数,表示最大长度。
数据范围
1≤N≤1000,10 − 9 ^{-9} 9 ≤数列中的数≤10 9 ^9 9
输入样例

7
3 1 2 1 8 5 6

输出样例

4

时间复杂度O(n 2 ^2 2)做法:

#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=1e3+7;
int dp[maxn],a[maxn];
int vis[maxn],ok,t[maxn];
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;++i) scanf("%d",&a[i]);
    int ans=0;dp[1]=1;
    for(int i=2;i<=n;++i)
    {
        dp[i]=1;
        for(int j=1;j<i;++j){
            if(a[j]<a[i]&&dp[i]<dp[j]+1){
                dp[i]=dp[j]+1;
                vis[i]=j;   //记录状态
            }
           // ans=max(ans,dp[i]);
           if(ans<dp[i]) ok=i,ans=dp[i];   //记录最优解
        }
    }
    /***
    输出最优解的序列
    for(int i=ans;i>0;--i){
        t[i]=a[ok];
        ok=vis[ok];
    }
    for(int i=1;i<=ans;++i) printf("%d ",t[i]);puts("");
    ***/
    printf("%d\n",ans);
    return 0;
}

二分做法:
时间复杂度O(nlog 2 _2 2n)

#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=1e3+7;
int a[maxn],res[maxn];
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;++i) scanf("%d",&a[i]);
    
    int cnt=0;
    for(int i=0;i<n;++i)
    {
        int l=0,r=cnt;
        while(l<r)
        {
            int mid=(l+r+1)>>1;
            if(res[mid]<a[i]) l=mid;
            else r=mid-1;
        }
        cnt=max(cnt,l+1);
        res[l+1]=a[i];    //保存最长的严格单调的最长上升子序列
    }
  //  for(int i=1;i<=cnt;++i) printf("%d ",res[i]);puts("");
    printf("%d\n",cnt);
    return 0;
}

地宫取宝

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

2 3 2
1 2 3
2 1 5

输出样例

14

解题思路
在这里插入图片描述

#include <cstdio>
#define rep(i,a,b) for(auto i=(a);i<=(b);++i)
using namespace std;
const int mod=1e9+7;
int dp[55][55][15][15],a[55][55];
int main()
{
    int n,m,k;
    scanf("%d%d%d",&n,&m,&k);
    rep(i,1,n)
        rep(j,1,m) scanf("%d",&a[i][j]),a[i][j]++;

    dp[1][1][0][0]=dp[1][1][1][a[1][1]]=1;
     //f[i][j][x][y]表示:在(i, j)这个点,拿了x 个物品,这些物品中价值最大的是y
    rep(i,1,n)
        rep(j,1,m)
            rep(x,0,k)
                rep(y,0,13){   遍历的是物品价值为y的值
                     //不拿物品
                    dp[i][j][x][y]=(dp[i][j][x][y]+dp[i-1][j][x][y])%mod;
                    dp[i][j][x][y]=(dp[i][j][x][y]+dp[i][j-1][x][y])%mod;
                    //可以拿
                    if(x&&y==a[i][j]){   //如果这次拿了的话,这次拿的物品一定是最大的
                        rep(z,0,y-1){
                        /***
                        取了当前这个点的物品,那么就要把“上一步”可能在的两个点中,手上的所有物
                        品最大值比当前这个位置的物品最大值小的累加起来,因为求的是总方案数嘛。
                        ***/
                            dp[i][j][x][y]=(dp[i][j][x][y]+dp[i-1][j][x-1][z])%mod;
                            dp[i][j][x][y]=(dp[i][j][x][y]+dp[i][j-1][x-1][z])%mod;
                        }
                    }
                }

    int res=0;
    rep(i,1,13) res=(res+dp[n][m][k][i])%mod;
    printf("%d\n",res);
    return 0;
}

波动数列

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

4 10 2 3

输出样例

2

样例解释:两个满足条件的数列分别是2 4 1 3和7 4 1 -2。
解题思路
设第一个数为 x x x,则第二个数为 x + d 1 x+d_1 x+d1,第三个数为 x x x+ d 1 d_1 d1+ d 2 d_2 d2…。
这里的 d 1 d1 d1 d 2 d2 d2表示 a a a或者 − b {−b} b,所以这个数列为:
x , x x, x x,x+ d 1 d_1 d1, x x x+ d 1 d_1 d1+ d 2 d_2 d2, x x x+ d 1 d_1 d1+ d 2 d_2 d2+ d 3 d_3 d3, …, x x x+ d 1 d_1 d1+ d 2 d_2 d2+…+ d n − 1 d_{n−1} dn1,又因为数列之和为 s s s,所以转化成:
n ∗ x + ( n − 1 ) ∗ n∗x+(n−1)∗ nx+(n1) d 1 d_1 d1 + ( n − 2 ) ∗ (n−2)∗ (n2) d 2 d_2 d2+ ( n − 3 ) ∗ (n−3)∗ (n3) d 3 d_3 d3+…+ d n − 1 d_{n−1} dn1= s s s
再在一步转化:
( s − [ ( n − 1 ) ∗ (s−[(n−1)∗ (s[(n1) d 1 d_1 d1+ ( n − 2 ) (n−2) (n2) d 2 d_2 d2+ ( n − 3 ) ∗ (n−3)∗ (n3) d 3 d_3 d3+…+ d n − 1 d_{n−1} dn1]) n n n= x x x
因为 x x x 是任意整数,所以又转化成:
s s s ( n − 1 ) ∗ (n−1)∗ (n1) d 1 d_1 d1+ ( n − 2 ) ∗ (n−2)∗ (n2) d 2 d_2 d2+ ( n − 3 ) ∗ (n−3)∗ (n3) d 3 d_3 d3+…+ d n − 1 d_{n−1} dn1 x x x 的余数相同
dp[i][j]:所有只考虑前 i 项,且当前的总和除以 n 的余数是 j 的方案的集合。
状态转移:dp[i][j]=dp[i-1][(j-ia)%n]+dp[i-1][(j+ib)%n];

#include <cstdio>
using namespace std;
typedef long long ll;
const ll maxn=1e3+7,mod=1e8+7;
ll dp[maxn][maxn];
ll work(int a,int b)
{
    return (a%b+b)%b;
}
int main()
{
    int n,s,a,b;
    scanf("%d%d%d%d",&n,&s,&a,&b);
    
    dp[0][0]=1;
    for(int i=1;i<n;++i)
        for(int j=0;j<n;++j){
            ll ta=work(j-(n-i)*a,n);
            ll tb=work(j+(n-i)*b,n);
            dp[i][j]=(dp[i-1][ta]+dp[i-1][tb])%mod;
        }
    ll t=work(s,n);
    printf("%lld\n",dp[n-1][t]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

幸愉聊信奥

谢谢亲的支持,我会继续努力啦~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值