例一:墙壁涂色问题:
共有n面墙壁围成一圈,共有k种颜色,相邻墙壁不能涂同一种颜色,问共有多少种涂色方案?
我们可以想到利用dp[i][j]
代表第i面墙涂第j种颜色的方案数,初始化dp[1][1] = 1
,利用递推公式dp[i][j] += dp[i - 1][k] (其中k != j)
,最后ans += dp[n][k] (其中k != 1)
,ans *= k
即为答案
按照上面的思路,我们发现它需要开二维数组,空间复杂度为O(n * k)
,时间复杂度也为O(n * k)
如果题目中n * k
的数据范围超过1e7该怎么办呢?
这时,我们就可以通过重新定义状态以去掉那些冗余项来对其进行优化,大家可以先考虑考虑如何进行优化成时间和空间复杂度均为O(n)再接着往下看~
这时,我们可以设dp[i]
代表第i面墙与第一面墙成环的方案数,初始化dp[1] = 1, dp[2] = k - 1, dp[3] = (k - 2) * dp[2]
。
我们可以发现,第i面墙与第一面墙成环,那么需要保证第i面墙与第1面颜色不同,也与第i - 1面墙颜色不同。由于dp[i - 1]
代表i - 1面墙成环的方案数,因此i - 1面墙与第一面墙颜色不同,所以当第i - 1面墙与第一面墙颜色不同的情况下,dp[i]
有dp[i - 1] * (k - 2)
种方案。
除此之外,第i - 1面墙也可以与第一面墙相同。这时假若第i - 1面墙与第一面墙颜色相同,那么前i - 1面墙的方案数为dp[i - 2]
,由于第一面和第i - 1面墙颜色相同,因此在该种情况下第i面墙的方案数为dp[i - 2] * (k - 1)
。
这两种情况的总和就是dp[i]
的方案总数,即当i > 3时:dp[i] = dp[i - 1] * (k - 2) + dp[i - 2] * (k - 1)
最终dp[n] *= k
即为答案
给出这道题的解题代码(由于数据范围爆longlong因此用大数):
#include <stdio.h>
int dp[1005][2005] = {0};
int main () {
int n, k;
scanf("%d%d", &n, &k);
dp[1][0] = dp[2][0] = dp[3][0] = dp[1][1] = 1;
dp[2][1] = k - 1;
dp[3][1]= (k - 2) * dp[2][1];
if (dp[3][1] >= 10) {
dp[3][0]++;
dp[3][2] = dp[3][1] / 10;
dp[3][1] %= 10;
}
for (int i = 4; i <= n; i++) {
dp[i][0] = dp[i - 1][0];
for (int j = 1; j <= dp[i][0]; j++) {
dp[i][j] = (k - 2) * dp[i - 1][j] + (k - 1) * dp[i - 2][j];
}
for (int j = 1; j <= dp[i][0]; j++) {
if (dp[i][j] >= 10) {
dp[i][j + 1] += dp[i][j] / 10;
dp[i][j] %= 10;
}
}
int j = dp[i][0] + 1;
while(dp[i][j]) {
dp[i][0]++;
if (dp[i][j] >= 10) {
dp[i][j + 1] = dp[i][j] / 10;
dp[i][j] %= 10;
}
j++;
}
}
for (int i = 1; i <= dp[n][0]; i++) {
dp[n][i] *= k;
}
for (int i = 1; i <= dp[n][0]; i++) {
if(dp[n][i] >= 10) {
dp[n][i + 1] += dp[n][i] / 10;
dp[n][i] %= 10;
}
}
if (dp[n][dp[n][0] + 1]) dp[n][0]++;
for (int i = dp[n][0]; i > 0; i--) {
printf("%d", dp[n][i]);
}
printf("\n");
return 0;
}
如果有写的不对或者不全面的地方 可通过主页的联系方式进行指正,谢谢