acwing蓝桥杯c++A/B组辅导课--第三讲 数学与简单dp

1.买不到的数目

小明开了一家糖果店。

他别出心裁:把水果糖包成4颗一包和7颗一包的两种。

糖果不能拆包卖。

小朋友来买糖的时候,他就用这两种包装来组合。

当然有些糖果数目是无法组合出来的,比如要买 10 颗糖。

你可以用计算机测试一下,在这种包装情况下,最大不能买到的数量是17。

大于17的任何数字都可以用4和7组合出来。

本题的要求就是在已知两个包装的数量时,求最大不能组合出的数字。

输入格式

两个正整数 n,m,表示每种包装中糖的颗数。

输出格式

一个正整数,表示最大不能买到的糖数。

数据范围

2≤n,m≤1000
保证数据一定有解。

输入样例:

4 7

输出样例:

17

 

本题考查性质:两个数p,q,最大不能凑出的数是(p-1)*(q-1)-1

可以用打表的方式找规律

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int main()
{
    int p,q;
    cin>>p>>q;
    cout<<(p-1)*(q-1)-1;
    return 0;
}

2.蚂蚁感冒

长 100 厘米的细长直杆子上有 n 只蚂蚁。

它们的头有的朝左,有的朝右。

每只蚂蚁都只能沿着杆子向前爬,速度是 1 厘米/秒。

当两只蚂蚁碰面时,它们会同时掉头往相反的方向爬行。

这些蚂蚁中,有 1 只蚂蚁感冒了。

并且在和其它蚂蚁碰面时,会把感冒传染给碰到的蚂蚁。

请你计算,当所有蚂蚁都爬离杆子时,有多少只蚂蚁患上了感冒。

输入格式

第一行输入一个整数 n, 表示蚂蚁的总数。

接着的一行是 n 个用空格分开的整数 Xi, Xi 的绝对值表示蚂蚁离开杆子左边端点的距离。

正值表示头朝右,负值表示头朝左,数据中不会出现 0 值,也不会出现两只蚂蚁占用同一位置。

其中,第一个数据代表的蚂蚁感冒了。

输出格式

输出1个整数,表示最后感冒蚂蚁的数目。

数据范围

1<n<50,
0<|Xi|<100

输入样例1:

3
5 -2 8

输出样例1:

1

输入样例2:

5
-10 8 -20 12 25

输出样例2:

3

本题思路:

关键:两只蚂蚁相遇会掉头,我们可以看做两只蚂蚁“灵魂交换”,或者说是相遇之后两只蚂蚁身份交换,1蚂蚁变成了2蚂蚁继续向前,2蚂蚁变成了1蚂蚁继续向前,相当于每只蚂蚁不掉头继续向前走,两只蚂蚁互相穿过

情况1:

第一只蚂蚁坐标轴上的位置,左边有向右的蚂蚁数left,右边有向左的蚂蚁right,left与right都不为0

根据蚂蚁间的穿过性

第一只蚂蚁左边向右的蚂蚁会被全部感染,第一只蚂蚁右边向左的蚂蚁也会被感染(因为前面左边向右的蚂蚁被感染后,会与这些右边向左的蚂蚁相遇),总感染数res=left+right+1(自身)

情况2:

如果left=0(左边向右为0)则,左边的蚂蚁永不被感染

2.1如果此时第一只蚂蚁的方向也向左(distance<0),那么它也无法感染右边的蚂蚁,总感染数res=1

2.2如果此时第一只蚂蚁方向向右,那么它右边向左的right只蚂蚁被感染,res=right+1(可归到情况1)

情况3:

如果right=0(右边向左为0),右边的蚂蚁永不被感染

3.1如果此时第一只蚂蚁方向也向右,那么它也无法感染左边的蚂蚁,总感染数res=1

3.2如果此时第一只蚂蚁方向向左,那么它能感染左边向右的蚂蚁,总感染数res=left+1(可归到情况1)

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 55;
int x[N];
int main()
{
    int n;
    cin>>n;
    for(int i=0;i<n;i++) cin>>x[i];

    int left=0,right=0;
    for(int i=1;i<n;i++)
    {
        if(abs(x[i])<abs(x[0]) && x[i]>0) left++;
        if(abs(x[i])>abs(x[0]) && x[i]<0) right++;
    }
    if((left==0 && x[0]<0)||(right==0 && x[0]>0)) cout<<1<<endl;
    else cout<<left+right+1<<endl;
    return 0;
}

3.饮料换购

乐羊羊饮料厂正在举办一次促销优惠活动。乐羊羊C型饮料,凭3个瓶盖可以再换一瓶C型饮料,并且可以一直循环下去(但不允许暂借或赊账)。

请你计算一下,如果小明不浪费瓶盖,尽量地参加活动,那么,对于他初始买入的 n 瓶饮料,最后他一共能喝到多少瓶饮料。

输入格式

输入一个整数 n,表示初始买入的饮料数量。

输出格式

输出一个整数,表示一共能够喝到的饮料数量。

数据范围

0<n<10000

输入样例:

100

输出样例:

149

根据题意做即可,喝一瓶饮料n--,cnt++,多一个盖子num++,(num==3)3个盖子换一个饮料n++,n-=3 

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int main()
{
    int n;
    cin>>n;
    int cnt=0;//喝了多少瓶子饮料
    int num=0;//盖子的数量
    while (n)
    {
        n--;//喝了一瓶
        cnt++;
        num++;
        if(num==3) 
        {
            n++;
            num-=3;
        }
    }
    cout<<cnt<<endl;
    return 0;
}

4.01背包问题

本题是模板题

01背包问题:
状态表示:
f[i][j]:
集合:从前i个物品中选,总体积不超过j的所有选法
属性:获得价值的最大值

状态计算:
不选第i件物品 f[i-1][j]
选第i件物品 f[i-1][j-v[i]]+w[i]
f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i])

一维优化
f[j]=max(f[j],f[j-v]+w);
//这里的j要从大到小枚举
原因在于:
f[j]如果用f[j-v]+w更新,f[j-v]是i-1层的状态,如果从小到大枚举,f[j-v]先被更新完了
变成了第i层的状态了,再用它来更新是不是用i-1层的状态来更新第i层状态了

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

5.摘花生

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

 

数字三角形模型的题目:
状态表示:
f[i][j]
集合:从起点(1,1)走到(i,j)的所有走法的集合
属性:获取的最大花生数量
状态计算:
看一下上一步是从哪走来的
1.最后一步向东走走到(i,j) f[i][j]=f[i][j-1]+w[i][j]
2.最后一步向南走走到(i,j) f[i][j]=f[i-1][j]+w[i][j]

f[i][j]=max(f[i][j-1],f[i-1][j])+w[i][j]

初始f[1][1]=w[1][1];

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110;
int w[N][N];
int f[N][N];
int main()
{
    int T;
    cin>>T;
    while(T--)
    {
        int n,m;
        cin>>n>>m;
        memset(w,0,sizeof w);
        memset(f,0,sizeof f);
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                cin>>w[i][j];
            }
        }
        f[1][1]=w[1][1];
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                if(i>=2) f[i][j]=max(f[i][j],f[i-1][j]+w[i][j]);
                if(j>=2) f[i][j]=max(f[i][j],f[i][j-1]+w[i][j]);
            }
        }
        cout<<f[n][m]<<endl;
    }
    return 0;
}

6.最长上升子序列

给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。

输入格式

第一行包含整数 N。

第二行包含 N 个整数,表示完整序列。

输出格式

输出一个整数,表示最大长度。

数据范围

1≤N≤1000,
−10的9次方≤数列中的数≤10的9次方

输入样例:

7
3 1 2 1 8 5 6

输出样例:

4

最长上升子序列模型:
状态表示:
f[i]
集合:所有以a[i]结尾的上升子序列
序列:长度最大值
状态计算:
已知该上升子序列最后一个数为a[i]
看看它的倒数第2个数是多少?
倒数第二个数的范围是a[0到i-1]中比a[i]小的数
所以f[i]=max(f[i],f[k]+1)(k=0,1,2...,i-1且a[k]<a[i])

二分优化的版本等到写提高课题解的时候再做总结 

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010,INF = 0x3f3f3f3f;
int a[N],f[N];
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    f[1]=1;
    for(int i=1;i<=n;i++)
    {
        f[i]=1;//以a[i]结尾上升子序列长度最少为1
        for(int k=1;k<i;k++)
        {
            if(a[k]<a[i]) f[i]=max(f[i],f[k]+1);
        }
    }
    int res=-INF;
    for(int i=1;i<=n;i++)
    {
        res=max(res,f[i]);
    }
    cout<<res<<endl;
    return 0;
}

7.地宫取宝

X 国王有一个地宫宝库,是 n×m 个格子的矩阵,每个格子放一件宝贝,每个宝贝贴着价值标签。

地宫的入口在左上角,出口在右下角。

小明被带到地宫的入口,国王要求他只能向右或向下行走。

走过某个格子时,如果那个格子中的宝贝价值比小明手中任意宝贝价值都大,小明就可以拿起它(当然,也可以不拿)。

当小明走到出口时,如果他手中的宝贝恰好是 k 件,则这些宝贝就可以送给小明。

请你帮小明算一算,在给定的局面下,他有多少种不同的行动方案能获得这 k 件宝贝。

输入格式

第一行 3 个整数,n,m,k,含义见题目描述。

接下来 n 行,每行有 m 个整数 Ci 用来描述宝库矩阵每个格子的宝贝价值。

输出格式

输出一个整数,表示正好取 k 个宝贝的行动方案数。

该数字可能很大,输出它对 1000000007 取模的结果。

数据范围

1≤n,m≤50,
1≤k≤12,
0≤Ci≤12。

输入样例1:

2 2 2
1 2
2 1

输出样例1:

2

输入样例2:

2 3 2
1 2 3
2 1 5

输出样例2:

14

dp分析:
状态表示:
f[i][j][k][c]:
集合:从起点走到了(i,j)点,且已经拿了k件宝物,手上最大价值为c的所有走法
属性:走法的数量count

状态计算:
1.如果没拿(i,j)位置上的物品,
1.1上一步从上方走来
f[i-1][j][k][c](c属于[0,13]每件物品的价值范围,0代表一件物品没选)
1.2上一步从左方走来
f[i][j-1][k][c]

f[i][j][k][c]=(f[i-1][j][k][c]+f[i][j-1][k][c])%mod

2.如果拿了第(i,j)位置上的物品
2.1上一步从上方走来
f[i-1][j][k-1][c'](c'<c即可,因为(i-1,j)这个位置的物品可能没拿,所以不一定就是a[i-1][j])
2.1上一步从左方走来
f[i][j-1][k-1][c'](c'<c)

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 55,Mod = 1000000007;
int a[N][N];
int f[N][N][13][14];

int main()
{
    int n,m,k;
    cin>>n>>m>>k;//n*m,恰好取k件物品
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            cin>>a[i][j];
            a[i][j]+=1;
            //因为题中0≤Ci≤12,但价值从1开始的话,那么价值为0就可以表示什么都没拿,更加方便
            //反正结果是方案数,不影响结果
        }
    }
    f[1][1][0][0]=1;//在起点,什么都没拿
    f[1][1][1][a[1][1]]=1;//在起点,拿了第一件物品
    
    //枚举每个物品,其价值为a[i][j]
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            if(i==1&&j==1) continue;
            
            //枚举当前已经拿了几个物品
            for(int cnt=0;cnt<=k;cnt++)
            {
                //如果没取(i,j)上的物品
                for(int c=0;c<=13;c++)
                {
                    f[i][j][cnt][c]=(f[i][j][cnt][c]+f[i-1][j][cnt][c])%Mod;
                    f[i][j][cnt][c]=(f[i][j][cnt][c]+f[i][j-1][cnt][c])%Mod;
                    //如果取了第(i,j)上的物品,说明第(i,j)上的物品是当前最大价值
                    if(c==a[i][j] && cnt>=1)//取了第(i,j)上的物品,最少拿了一件
                    {
                        for(int c1=0;c1<a[i][j];c1++)//枚举一下没取a[i][j]之前最大价值
                        {
                            f[i][j][cnt][c]=(f[i][j][cnt][c]+f[i-1][j][cnt-1][c1])%Mod;
                            f[i][j][cnt][c]=(f[i][j][cnt][c]+f[i][j-1][cnt-1][c1])%Mod;
                        }
                    }
                }
            }
        }
    }
    int res=0;
    for(int c=0;c<=13;c++)
    {
        res=(res+f[n][m][k][c])%Mod;
    }
    cout<<res<<endl;
    return 0;
}

8.波动数列 

观察这个数列:

1 3 0 2 -1 1 -2 …

这个数列中后一项总是比前一项增加2或者减少3,且每一项都为整数

栋栋对这种数列很好奇,他想知道长度为 n 和为 s 而且后一项总是比前一项增加 a 或者减少 b 的整数数列可能有多少种呢?

输入格式

共一行,包含四个整数 n,s,a,b 含义如前面所述。

输出格式

共一行,包含一个整数,表示满足条件的方案数。

由于这个数很大,请输出方案数除以 100000007 的余数。

数据范围

1≤n≤1000,
−10的9次方9≤s≤10的9次方,
1≤a,b≤10的6次方

输入样例:

4 10 2 3

输出样例:

2

样例解释

两个满足条件的数列分别是2 4 1 3和7 4 1 -2。

解析:

附:同余的定义是这样的:
给定一个正整数n,如果两个整数a和b满足a-b能被n整除,即(a-b) mod n=0,
那么就称整数a与b对模n同余,记作a≡b(mod n),同时可成立a mod n = b。
也就是相当于a被n整除余数等于b的意思

题目:
设波动序列的第一个数为x,后一个数=前一个数+d(d=+a/-b)
x+(x+d1)+(x+d1+d2)+...+(x+d1+d2+..+dn-1)=s;
n*x+(n-1)*d1+(n-2)*d2+...+2*dn-2+dn-1=s;
s-[(n-1)*d1+(n-2)*d2+...+2*dn-2+dn-1]=n*x

x = {s-[(n-1)*d1+(n-2)*d2+...+2*dn-2+dn-1]} / n
将(n-1)*d1+(n-2)*d2+...+2*dn-2+dn-1记为s'

s已知,n已知,对每个d取值,得到不同的s'
如果s-s'是n的倍数,即(s-s')mod n=0,即s≡s'(mod n),即s mod n = s',即s除以n的余数是s'
那么x就是个合法的整数,由x和取值的d可以得到一整组波动序列

这样就把原问题转化为了一个组合问题,背包问题的根源就是组合问题,每种物品选或不选,看看有多少
s'满足题意

dp分析:
状态表示
f[i][j]:
集合:从前i项中对每个d取值,求得当前总和s',且s'mod n = j的所有方案
属性:方案的数量
状态计算:

1.第i项中d取+a 
(n-1)*d1+(n-2)*d2+...+(n-i+1)*dn-i+1+(n-i)*a mod n = j;
((n-1)*d1+(n-2)*d2+...+(n-i+1)*dn-i+1+(n-i)*a-j) mod n = 0;
(n-1)*d1+(n-2)*d2+...+(n-i+1)*dn-i+1 mod n =(j-(n-i)*a) mod n
f[i][j]=f[i-1][(j-(n-i)*a) mod n]

2.第i项中d取-b
(n-1)*d1+(n-2)*d2+...+(n-i+1)*dn-i+1-(n-i)*b mod n = j;
((n-1)*d1+(n-2)*d2+...+(n-i+1)*dn-i+1-(n-i)*b-j) mod n = 0;
(n-1)*d1+(n-2)*d2+...+(n-i+1)*dn-i+1 mod n= (j+(n-i)*b) mod n
f[i][j]=f[i-1][(j+(n-i)*b) mod n]

所以f[i][j]=f[i-1][(j-(n-i)*a) mod n]+f[i-1][(j+(n-i)*b) mod n]

注意:这里的mod得到的数由于是数组下标,所以一定得为正数
用此公式即可
int get_mod(int a,int b)
{
    return (a%b+b)%b;
}

x = {s-[(n-1)*d1+(n-2)*d2+...+2*dn-2+dn-1]} / n
由此可知i最大是n-1,从前n-1个d中选择每个d的值,得到s,j=s mod n的所有方案
所以结果是f[n-1][get_mod(s,n)] s mod n也可能是负数(因为s可能为负)

 

#include <iostream>
#include <cstring>
#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;//啥都不选,1种方案
    for(int i=1;i<=n-1;i++)
    {
        for(int j=0;j<n;j++)//j是当前和s'mod n的值,范围是[0,n-1]
        {
            f[i][j]=(f[i-1][get_mod(j-(n-i)*a,n)]+f[i-1][get_mod(j+(n-i)*b,n)])%Mod;
        }
    }
    cout<<f[n-1][get_mod(s,n)];
    return 0;
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值