一维线性 dp例题-ACM 入场劵

 题目:ACM入场券  

【问题描述】

ACM协会举办第1次活动,要为每个队员发入场卷,入场卷分别有A、C、M三种,每个人可以任意拿其中的一种,排队入场,要求每相邻两个不能同时为A;

例如:只有一个人排队,则有三种即:A、C、M

两个人排队,则有八种即:AC、AM、CA、CC、CM、MA、MC、MM

输入n个人,计算有多少种不同的方案,可能数很大,结果用1000007取余

【输入格式】

一个正整数n:表示入场的人数

【输出格式】

输出:方案数m

【样例输入1】

1

【样例输出2】

3

【样例输入2】

2

【样例输出2】

8

【数据规模】

规模:3<=N<1000000

算法分析:

1.首先,根据其规模可知,不能用常规的搜索来做,也无法用贪心,都会超时,因此就可以先往动态规划这方面思考。

2.dp[i][j]定义:这里我们就直接先定义:

        dp[1000010][3] ,其中分为以下三种情况:

        (1)表示前i个人排队时,最后一个人选择A的方案数(含A的个数);

                则 dp[i][0] = dp[i-1][1]

​​​​​​​                对于推导 dp[i][0] 的逻辑,根据题目要求,相邻的两个人不能同时选择A。因此,对

                于第i个人选择A的情况,只能由前一个人不选择A的方案数推出。因为如果前一个人选                

                择A,则第i个人不能选择A,只能选择C或M.

                所以应该为:

                dp[i][0] = dp[i-1][1]


        (2)表示前i个人排队时,最后一个人不选择A的方案数(不含A的个数);

                 dp[i][1] 表示不含A的个数,根据题目要求,相邻的两个人不能同时为A。

                因此,对于第 i 个人不含A的情况,可以有两种情况:
                
1. 第i-1个人选择A,第i个人选择C或M;
                  2. 第i-1个人选择C或M,第i个人选择C或M。

                由于每个人可以选择C或M,所以每种情况下都有两种选择,因此需要乘以2。这样计算                

                出的 dp[i][1] 表示的是不含A的方案数,且满足相邻两个人不能同时为A的条件

                所以正确的的应该为:        

                dp[i][1] = dp[i-1][2]*2


        (3)表示前i个人排队时的总方案数。

                综上,便可以很轻松的得出:

                dp[i][2] = dp[i] +​​​​​​​ dp[i-1]

        3.dp数组的初始化:

        (1)当 i 为 0 时,很明显,dp[0][j] = 0,啥也没有嘛,还装蒜😂😂,直接赋值为 0。

                当然其实严格意义上讲,dp[0][j]本身也 没有意义,赋值为任何值都不影响状态转移方

                程的递推程。

        (2)当 i=1 时,我们也可以很轻易得到如下:
                dp[1][0] = 1

                dp[1][1] = 2

                dp[1][2] = 3

完整代码如下:

#include<bits/stdc++.h>
using namespace std;
// dp数组定义及初始化:
int dp[1000010][3] = {{0,0,0}, {1,2,3}};
int main(){
    int i, n;
    cin >> n;
    // 下面每个递推公式都用 1000007 求余了,是避免在递推过程中栈溢出,
    // 而且题目要求的也只是最终答案用 1000007 取余数结果而已
    for(i=2; i<=n; i++){
        // 前 i 的人排队,最后一个选 A 的方案数
        dp[i][0] = dp[i-1][1] % 1000007;
        // 前 i 的人排队,最后一个不选 A 的方案数
        dp[i][1] = dp[i-1][2] * 2 % 1000007;
        // 前 i 个人排队时的总方案数
        dp[i][2] = (dp[i][0] + dp[i][1]) %1000007;
    }
    // 输出结果:前 i 个人排队时的总方案数如下
    cout << dp[n][2] << endl;
    
    return 0;
}

解法二:

其实该题也可以根据数学方法推导有出来,从 n>=3 开始有以下规律:

n = 1 ,  ans(1) = 3; 

n = 2,  ans(2) = 8;

n = 3,  ans(3) = 22;

...

n时, ans(n) = ( ans(n-1) +ans(n-2) ) * 2

这里就不给大家推导了哈,大家可以去自己推导一下试试:

该方法完整代码如下:

#include<bits/stdc++.h> 
using namespace std; 
int main(){
	int a,b,c,ans,n; 
		a=3,b=8;
	cin>>n;
	if(n==1) {
		cout<<3;
		return 0;
	}
	if(n==2){
		cout<<8;
		return 0;
	} 
	for(int i=3;i<=n;i++){
		c=((a+b)*2)%1000007;
		a=b;
		b=c;
	}
	cout<<c<<endl; 
}

总结归纳:

关于这个题以及这一类型的题,咱们就关注两个点:

1.判断该题是否能用常规的搜索、贪心直接做,如果不能且没有思路就往动规上面思考。

2.确定了要用dp,就继续思考我们要设的 dp 定义是什么?(根据题目答案方面考虑,如该题我们给出的dp数组的定义,那三种情况),因为确定了、清楚了 dp 定义,我们才能进行状态转移方程的思考,不然假如随便给你一个 01 背包的状态转移方程,dp 定义都不清楚的人就更别提理解该状态转移方程了

一般关于动态规划的题目,上面第二点考虑清楚了,状态转移方程就确定了,该题就基本可以拿下了。

  • 24
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值