题目:666RPG
说来实在惭愧,已经很久没有更新博客了。(反正也没人看-.-//偷笑)
那么今天就更新一道dp类型的题目。
所谓:dp大法好,好到头发少。
另外需要补充的一点是呢,这题我在时限内并没有写出来,可谓菜的开花(直接开花)。但是我觉得这个dp的手法十分有用,所以我就很厚脸皮更上了一期,当然也会尽量把思路说的的比较详细啦。
看题!!:
牛客练习赛41-B题
思路:
很明显的是一道dp题,但是状态转移方程怎么推,dp数组又怎么开,完全没有思路。
一刚开始以为是倒着dp,而且肯定时按次数dp的,于是先是认为确定距离最后一个数的偶数个数变化成为-666:
比如10个数据的话,那就是10,8,6,4,2转变成-666,因为后面一定是偶数个时才能不变换符号。这样就找到了子问题,但是,问题就来了,dp数组怎么办,凉凉。
所以,上面的思路全是废话,从这里看起:
因为300*666(总和)数据并不算很大,可以直接用数组记录下来和,数组的第一个维度的大小就是300 * 666的两倍,两倍,因为有正负情况。另外还可以用滚动数组减小空间的使用,注意取mod
(下面有介绍滚动数组是个啥)
状态转移方程:dp[i][maxn+j] = dp[i-1][maxn+j-a[i]]+dp[i-1][maxn-j];
看代码:
#include<bits/stdc++.h>
using namespace std;
const double eps = 1e-6;
const int inf=1e9;
const int maxn=300*700;//比300*666略大就行
const int mod = 1e8+7;
#define MAX_N 100007
#define MAX_M 10001
#define ll long long
int dp[2][maxn*2];
int a[301];
int main()
{
#ifdef LOCAL
freopen("test.txt","r",stdin);
#endif
int n;
cin >> n;
dp[0][maxn]=1; //最开始是0,对应的是maxn+0,一开始;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
for(int j=300*-666;j<=300*666;j++){ //j代表的是上一次加出来的和,
if(j==666)
continue;
dp[i%2][maxn+j] = dp[i%2^1][maxn+j-a[i]]+dp[i%2^1][maxn-j]; //两种改变。
if(dp[i%2][maxn+j]>mod){
dp[i%2][maxn+j] -= mod;
}
}
}
printf("%d\n",dp[n%2][maxn-666]);
return 0;
}
其他类似的题目:
同样是牛客练习赛的40场,A题
这题与上面是比较相似的,就是dp每一个音符,符合条件的加上,看牛客提交代码也能看到的懂。
那么既然看的懂为啥要看我这个呢,因为我这个有注释啊,那为啥我在这里不注释呢。因为现在说的不是这一题,欸,无懈可击。(如果有实在不懂的朋友可以在下方留言,我在有时间的时候可以更一起,虽然也是厚脸皮式的,人菜木得办法,T_T)
滚动数组是个啥
滚动数组简单理解就是通过不断循环利用数组,来减少空间的使用,这种思想其使用的还是比较多的。
最常见的有一种就是斐波那契数列的实现 :
int a=1,b=1,c=a+b;
for(int i=0;i<100;i++){
a = b;
b = c;
c = a+b;
}
斐波那契额的递推公式f[i] = f[i-1]+f[i-2];所以实际上需要的数只有三个,就是上面代码中的a,b,c。
那么有些朋友可能会说了:
“哦,我知道了,那上面这个状态转移方程(dp[i][maxn+j] = dp[i-1][maxn+j-a[i]]+dp[i-1][maxn-j];)也是有两个相加的,也可以变成a,b,c三个数”。
不行!,为啥呢,因为一维上的下标是不确定的,但是从二维角度上来看,得到下一行的所有数值,只需要前面一行的所有数就行了,所以可以压缩成只有两行的二维数组。不知道这样大家懂了没有。