动态规划(八)

动态规划(八)(测验)


好了,直接开始吧。

夏令营第一阶段(Day8)问题A剪纸带(ribbon)

题目描述

现有一长n的纸带,它最多能被切多少条小纸带,且满足:每条小纸带的长度要么是a,要么是b,要么是c,如果一条都切不成功的话,输出-1。

输入

一行,四个整数分别表示n,a,b,c。1 ≤ n, a, b, c ≤ 4000。

输出

一个表示你的答案的整数。

样例输入

5 5 3 2

样例输出

2

那么直接开始

纸带长为n,可以剪掉长为a或b或c的长度,且每种长度又可以剪无数次,所以这题已经很明显了,就是一道完全背包。
所以我们得出动态转移方程为: f [ j ] = m a x ( f [ j a [ i ] ] + 1 , f [ j ] ) f[j]=max(f[j-a[i]]+1,f[j])
其中 i 表示第i种长度, j 表示纸带长度。

那么我们直接按照完全背包的方法去做就好了

Code:

#include<bits/stdc++.h>
using namespace std;
int a[5];
int n;
int f[50005];
const int oo=1e9;
int main(){
    scanf("%d",&n);
    for(int i=0;i<=50003;i++) f[i]=-oo; //初始化,不影响结果,且可以判断题中输出-1的情况
    f[0]=0;//初始化,不然会算出一些很奇怪的东西
    for(int i=1;i<=3;i++) scanf("%d",&a[i]); //就是题目中的a,b,c(为了更方便利用完全背包,因此存到数组内部
    for(int i=1;i<=3;i++){
        for(int j=a[i];j<=n;j++) //完全背包基本公式。
            f[j]=max(f[j-a[i]]+1,f[j]); //动态转移方程,f[j-a[i]]指的是在纸带长度为j-a[i]时的最优值,+1指的是剪去一段长度为a[i]的纸片,剪的次数加多了一次,所以+1,f[j]表示我不剪当前长度时的最优值。
    }
    if(f[n]<0){						//初始化的妙用
        printf("%d\n",-1);			//判断-1的情况
        return 0;
    }
    printf("%d\n",f[n]);			//输出最优答案
    return 0;
}

嗯,很简单的题。


问题 B: 夏令营第一阶段(Day8)问题B消除(pop)

题目描述

有n个整数。每次你选择其中一个数x,接着所有等于x-1或x+1的数和你选择的那个数都会消失,而你获得x个积分。问,当你消除掉所有整数时,最多能获得多少积分。

输入

第一行一个整数n。1 ≤ n ≤ 10 ^ 5。
第二行n个大于等于1,小于等于10 ^ 5的整数。

输出

一个表示你的答案的整数。

扫描二维码关注公众号,回复: 11437055 查看本文章

样例输入

9
1 2 1 3 2 2 2 2 3

样例输出

10

其实也很水

首先,我们可以把这一大堆数字看成一个数轴,所以如果我消除数字 i i ,那么 i 1 i-1 i + 1 i+1 两个数字就不需要消除了,因为已经被 i i 解决了。

我们可以用一个 s u m sum 数组记录消除数字 i i 所需要的价值。
所以我们得出一个动态转移方程: f [ i ] = m a x ( f [ i 2 ] + s u m [ i ] , f [ i 1 ] ) ; f[i]=max(f[i-2]+sum[i],f[i-1]);
其中 f [ i 2 ] + s u m [ i ] f[i-2]+sum[i] 表示如果我消除数字 i i 时所得到的答案,因为如果我们消除数字 i i ,则 i 1 i-1 i + 1 i+1 就无了,但是 i i 不能影响 i 2 i-2 所以我们需要加上当消除到 i 2 i-2 时的最优值。
f [ i 1 ] f[i-1] 则表示如果我不消除 i i 这个数字时的最优值,然后两个比较,要么消除要么不消除,比较其最优值就得出了 f [ i ] f[i] 的最优值。

嘤嘤嘤

Code:

#include<bits/stdc++.h>
using namespace std;
long long n;
long long c;
long long f[1000005];
long long sum[1000005];
int main(){
    cin>>n;
    for(long long i=1;i<=n;i++){
        long long x;
        cin>>x;
        sum[x]+=x;							//记录消除数字x时所需的代价
        c=max(c,x);							//求出最大的数字,在以后做DP的时候不做无谓的搜索。
    }
    f[1]=sum[1];
    for(long long i=2;i<=c;i++)
        f[i]=max(f[i-2]+sum[i],f[i-1]);		//得出的动态转移方程
    cout<<max(f[c],f[c-1])<<endl;			//输出消除c和c-1两种情况的最优值
    return 0;
}

完美


夏令营第一阶段(Day8)问题C传球游戏(ball)

题目描述

上体育课的时候,小蛮的老师经常带着同学们一起做游戏。这次,老师带着同学们一起做传球游戏。

游戏规则是这样的:n个同学站成一个圆圈,其中的一个同学手里拿着一个球,当老师吹哨子时开始传球,每个同学可以把球传给自己左右的两个同学中的一个(左右任意),当老师再次吹哨子时,传球停止,此时,拿着球没传出去的那个同学就是败者,要给大家表演一个节目。

聪明的小蛮提出一个有趣的问题:有多少种不同的传球方法可以使得从小蛮手里开始传的球,传了m次以后,又回到小蛮手里。两种传球的方法被视作不同的方法,当且仅当这两种方法中,接到球的同学按接球顺序组成的序列是不同的。比如有3个同学1号、2号、3号,并假设小蛮为1号,球传了3次回到小蛮手里的方式有1->2->3->1和1->3->2->1,共2种。

输入

输入共一行,有两个用空格隔开的整数n,m(3<=n<=30,1<=m<=30)。

输出

输出共一行,有一个整数,表示符合题意的方法数。

样例输入

3 3

样例输出

2

看一看,因为是围成一个圈,所以,传 i i 次后球传到第 j j 个人手上的方法数是多少呢?

因为第 j j 个人的两边分别是第 j 1 j-1 个人和第 j + 1 j+1 个人,所以第 j j 个人的方法数就是第 j 1 j-1 个人的方案数加上第 j + 1 j+1 个人的方案数之和,我们用一个 f f 数组来表示第 j j 个人的最多方案数,然后这个还有步数的限制,所以数组还需要多开一维, f [ i ] [ j ] f[i][j] 就表示第 i i 步时球传到第 j j 个人的方案数。

那么,第 i i 步时球传到第 j j 个人的方案数就是第 i 1 i-1 步时传到第 j 1 j-1 个人和第 j + 1 j+1 个人的方案数总和,所以
我们可以得到动态转移方程: f [ i ] [ j ] = f [ i 1 ] [ j 1 ] + f [ i 1 ] [ j + 1 ] f[i][j]=f[i-1][j-1]+f[i-1][j+1]
漂亮

Code:

#include<bits/stdc++.h>
using namespace std;
int n,m;
int f[35][35];
int main(){
    scanf("%d%d",&n,&m);
    f[0][1]=1; 						//设小蛮为第一个人。
    for(int i=1;i<=m;i++){
        for(int j=1;j<=n;j++){
            if(j==1)				//因为是一个圈,所以当j为1的时候需要特判
                f[i][j]=f[i-1][j+1]+f[i-1][n];
            else if(j==n) 			//同上
                f[i][j]=f[i-1][j-1]+f[i-1][1];
            else f[i][j]=f[i-1][j-1]+f[i-1][j+1]; //动态转移方程。
        }
    }
    printf("%d\n",f[m][1]);
    return 0;
}

夏令营第一阶段(Day8)问题D网格路1(grid1)

题目描述

小x住在左下角(0,0)处,小y在右上角(n,n)处。小x需要通过一段网格路才能到小y家。每次,小x可以选择下面任意一个方向前进:

右方:从(x,y)到点(x+1,y);
上方:从(x,y)到点(x,y+1);
右上方:从(x,y)到点(x+1,y+1)。
问小x有多少种走法到达小y家。

输入

一行,一个整数n。1 ≤ n ≤ 500.

输出

你的答案除以1000000007的余数。

样例输入

2

样例输出

13

又是一道非常水的题

这个动态转移方程应该一看就出来了吧

其实 f [ i ] [ j ] f[i][j] 就是 f [ i 1 ] [ j ] + f [ i ] [ j 1 ] + f [ i 1 ] [ j 1 ] f[i-1][j]+f[i][j-1]+f[i-1][j-1]

既然动态转移方程出来了

上代码!

Code:

#include<bits/stdc++.h>
using namespace std;
long long n;
long long f[505][505];
const long long mod=1000000007;
int main(){
    scanf("%lld",&n);
    n+=1; 				//题目中是(0,0)到(n,n),我为了防止数组越界,要+1,也会方便很多。
    f[0][0]=1; 			//初始化。
    for(long long i=1;i<=n;i++) 
        for(long long j=1;j<=n;j++)
            f[i][j]=(f[i-1][j-1]%mod+f[i-1][j]%mod+f[i][j-1]%mod)%mod; //动态转移方程。
    printf("%d\n",f[n][n]%mod); 
    return 0;
} 

夏令营第一阶段(Day8)问题E卡牌游戏(card)

题目描述

有三种卡牌,记为A,B,C类型。每轮,小x可以选择的出牌方法有:

打出一张A牌;

打出一张B牌;

打出一张A牌和一张C牌;

打出两张B牌和三张C牌。

问小x有多少种方法出完所有的牌。只要有一轮出牌的方法不一样,就算作不同的方法。

输入

一行,三个整数a,b,c,依次表示卡牌A,B,C的数量。1 ≤ a,b,c ≤ 15

输出

你的答案除以1 000 000 007的余数。

样例输入

2 2 3

样例输出

3

emmm…这题怎么做呢?

其实很简单

我们用一个三维数组 f f 来模拟,A,B,C三种卡牌的数量

那么, f [ i ] [ j ] [ k ] f[i][j][k] 表示A种卡牌数量为 i i ,B种卡牌数量为 j j ,C种卡牌的数量为 k k 的方案数。

而题目中给出的出牌规则是这样的:

打出一张A牌;

打出一张B牌;

打出一张A牌和一张C牌;

打出两张B牌和三张C牌。

那么

动态转移方程就是 f [ i ] [ [ j ] [ k ] = [ i 1 ] [ j ] [ k ] + f [ i ] [ j 1 ] [ k ] + f [ i 1 ] [ j ] [ k 1 ] + f [ i ] [ j 2 ] [ k 3 ] f[i][[j][k]=[i-1][j][k]+f[i][j-1][k]+f[i-1][j][k-1]+f[i][j-2][k-3]

Code:

#include<bits/stdc++.h>
using namespace std;
int a,b,c;
int f[55][55][55];
const int mod=1000000007;
int main(){
    cin>>a>>b>>c;
    f[0][0][0]=1; 						//初始化
    for(int i=0;i<=a;i++){
        for(int j=0;j<=b;j++){
            for(int k=0;k<=c;k++){		//枚举A,B,C卡牌的数量
                if(i>=1){f[i][j][k]+=f[i-1][j][k];f[i][j][k]%=mod;}	//DP,记得特判,不然会越界。下同。
                if(j>=1){f[i][j][k]+=f[i][j-1][k]%mod;f[i][j][k]%=mod;}
                if(i>=1 && k>=1){f[i][j][k]+=f[i-1][j][k-1]%mod;f[i][j][k]%=mod;}
                if(j>=2 && k>=3){f[i][j][k]+=f[i][j-2][k-3]%mod;f[i][j][k]%=mod;}
            }
        }
    }
    cout<<f[a][b][c]%mod<<endl;
    return 0;
}

夏令营第一阶段(Day8)问题F好的序列(seq)

题目描述

一个长为k的序列b1, b2, …, bk (1 ≤ b1 ≤ b2 ≤ … ≤ bk ≤ n),如果对所有的 i (1 ≤ i ≤ k - 1),满足bi | bi+1,那么它就是好的序列。这里a | b表示a是b的因子,或者说a能整除b。
给出n和k,求长度为k的好的序列有多少个。

输入

一行,两个整数n,k。1 ≤ n,k ≤ 2000

输出

你的答案除以1 000 000 007的余数。

样例输入

3 2

样例输出

5

样例说明

有如下5个序列:[1, 1], [2, 2], [3, 3], [1, 2], [1, 3]。


这题怎么说呢…

我的思路是这样的:用 f [ i ] [ j ] f[i][j] 来表示长度为 i i ,最大的数为 j j ,那么 这个等于多少呢?

我们可以这样:枚举一个因数 j j ,再枚举倍数 k k ,即可以得出 f [ i ] [ j k ] + = f [ i 1 ] [ j ] f[i][j*k]+=f[i-1][j] (因为下一个数一定是前一个数的倍数

那么,我们的核心代码就出来了:

for(int i=2;i<=k;i++){
        for(int j=1;j<=n;j++){
            for(int x=1;x<=n/j;x++) 		//枚举倍数,一定不超过n。
                f[i][x*j]=(f[i][x*j]+f[i-1][j]%mod)%mod;
        }
    }

然后,我们再把长为 k k 的序列的总可能数加起来,就是我们的答案了

Code:

#include<bits/stdc++.h>
using namespace std;
int n,k;
int f[2006][2006];
int ans;
const int mod=1000000007;
int main(){
    cin>>n>>k;
    for(int i=1;i<=n;i++)
        f[1][i]=1;
    for(int i=2;i<=k;i++){
        for(int j=1;j<=n;j++){
            for(int k=1;k<=n/j;k++)
                f[i][k*j]=(f[i][k*j]+f[i-1][j]%mod)%mod;
        }
    }
    for(int i=1;i<=n;i++) ans=(ans+f[k][i]%mod)%mod;
    cout<<ans;
    return 0;
}

夏令营第一阶段(Day8)问题G四面体(tetra)


题目描述

一只蚂蚁从点A出发,每次行动可沿四面体的边来到另外一个点。问n次行动后,蚂蚁回到点A有多少种方法。
如图

输入

一行,一个整数n。1 ≤ n ≤ 10^6

输出

你的答案除以1 000 000 007的余数。

样例输入

2

样例输出

3

康康这题

是不是似曾相识?

这不就传球游戏嘛!只不过每个点都可以到达另外三个点而已

水题!

Code:

#include<bits/stdc++.h>
using namespace std;
long long n;
long long f[1000000][6];
const long long mod=1000000007;
int main(){
    scanf("%lld",&n);
    f[0][1]=1;
    for(int i=1;i<=n;i++){
        f[i][1]=(f[i-1][2]%mod+f[i-1][3]%mod+f[i-1][4]%mod)%mod;
        f[i][2]=(f[i-1][1]%mod+f[i-1][3]%mod+f[i-1][4]%mod)%mod;
        f[i][3]=(f[i-1][2]%mod+f[i-1][1]%mod+f[i-1][4]%mod)%mod;
        f[i][4]=(f[i-1][2]%mod+f[i-1][3]%mod+f[i-1][1]%mod)%mod;
    }
    printf("%d\n",f[n][1]);
    return 0;
}

其实嘛,这套题也就这样吧

so,到这就完了!

撒花~~

某大佬的Blog(感谢这位大佬的帮助)

猜你喜欢

转载自blog.csdn.net/LOSER_LU/article/details/107039634