题目描述
存在n+1个房间,每个房间依次为房间1 2 3...i,每个房间都存在一个传送门,i房间的传送门可以把人传送到房间pi(1<=pi<=i),现在路人甲从房间1开始出发(当前房间1即第一次访问),每次移动他有两种移动策略:
A. 如果访问过当前房间 i 偶数次,那么下一次移动到房间i+1;
B. 如果访问过当前房间 i 奇数次,那么移动到房间pi;
现在路人甲想知道移动到房间n+1一共需要多少次移动;
输入描述:
第一行包括一个数字n(30%数据1<=n<=100,100%数据 1<=n<=1000),表示房间的数量,接下来一行存在n个数字 pi(1<=pi<=i), pi表示从房间i可以传送到房间pi。
输出描述:
输出一行数字,表示最终移动的次数,最终结果需要对1000000007 (10e9 + 7) 取模。
示例1
输入
2 1 2
输出
4
说明
开始从房间1 只访问一次所以只能跳到p1即 房间1, 之后采用策略A跳到房间2,房间2这时访问了一次因此采用策略B跳到房间2,之后采用策略A跳到房间3,因此到达房间3需要 4 步操作。
解析与总结
1.其实一开始我也没有思路,根源是我没有认真审题,第二是理解不透彻没有想通,题目条件pi(1<=pi<=i)是题目的关键,此题目如果不采用动态规划算法会超时。 2.我们可以将一排房间排列如下图所示,假设1房间为最前,i房间为最后,路人甲移动的目的就是移动到最后的房间,即第n个房间的下一个房间。 3.注意到条件pi(1<=pi<=i),由题目可知,如果路人甲目前处于房间i,那么他只能向后跳跃一格,或者跳跃至pi房间,而pi<=i,也就是说只有是i房间访问过偶数次,路人甲才有可能向后跳跃并到达终点。这句话意味着传送门不可能把你往后面的门传,如果你想向后走,那么你只能访问该房间偶数次;
4.下面是重点,也是理解本题思路的关键,假设你现在第一次到达i
门,你觉得前面i - 1
个房子你都访问了多少次?每个房子访问了多少次我不知道,但是我知道每个房子访问的次数都是偶数!这一点很重要。仔细想想,假如前面i - 1
中有一个房子的访问次数不是偶数次,那么,你不可能向后走,更不可能走到i
门。(如果是奇数就传送到前面的房间去了呀)
5.下面开始建模,找到动态规划递推关系,我们假设dp[i]为到达第i个房间,并且第一次进入次数为偶数,此时所用的移动次数。
6.下面根据图解来列递推关系,dp[i-1]为第一个偶数次到达i-1房间所移动的次数,那么此时他将会传送到后一个房间,也就是i房间,然而此时路人甲是第一次到达i房间,并不是偶数次到达,并不满足dp[i],因此我们先列一个关系:
dp[i] = dp[i - 1] + 1 //这里dp[i]是暂时的,还不是真正的dp[i]
7.此时路人甲是第一次到达i房间,他会传送到pi房间去,假设pi=k,由于i房间之前的房间都被访问过偶数次,此次到k房间,又是奇数次访问k房间。再列一个关系:
dp[i] = dp[i] + 1 //传送到pi房间去,也算移动一步
8.现在回归到这样一个问题了,就是现在处于奇数次访问k(k<=i)房间了,现在还需要移动多少步才能又回到i房间呢,我们假设x[k]为第一次到达k房间所用得步数,那么
x[k] = dp[k-1] + 1
那么从dp[k-1]移动到dp[i-1]需要移动多少步呢,当然是dp[i-1] - dp[k-1],
换句话说,根据这个关系,当处于奇数次访问k房间到再次访问i房间,还需要走dp[i-1] - dp[k-1]步。(因为此时k-1到k只需要一步,i-1到i也只需要一步,相互抵消)
9.综上,我们可以累加得到dp[i]和dp[i-1]的关系,此时dp[i-1]为第一次偶数次到达i-1房间的步数,dp[i]为第一次偶数次到达i房间的次数,递推关系如下:
dp[i] = dp[i-1] + 1 + 1 + dp[i-1]-dp[k-1] = 2 * dp[i-1] - dp[k-1] + 2
替换k即可:dp[i] = 2 * dp[i-1] - dp[ pi[i] - 1 ] + 2
10.下面以后端Java语言为例,贴出我的代码,根据题意,dp[1]输出1,根据递推关系,dp[0]=-1
import java.util.*;
public class Main {
public static void main(String[] args) {
int MOD = 1000000007;
Scanner sc = new Scanner(System.in);
int rooms = sc.nextInt();
//房间号为1-rooms
int[] pi = new int[rooms+1];
for(int i=1 ; i<=rooms ; i++) {
pi[i] = sc.nextInt();
}
int[] dp = new int[rooms+1];
//难道不是输出2吗,奇怪
if(rooms==1) {
//题意不明确,既然rooms==1时,结果输出1,那么只能dp[0]=-1了
System.out.println(1);
return;
}
//p1只能是1,因为1<=pi<=i
dp[0] = -1;
dp[1] = 1;
int k=2;
while(k <= rooms) {
//动态规划,递推关系
dp[k] = ((2* dp[k-1])%MOD - dp[pi[k]-1] +2 + MOD)%MOD;
k++;
}
System.out.println(dp[rooms] + 1);
}
}
11.其实我当时第一做法没想到动态规划,也贴出我的代码吧,通过率60%,算法超时了,现在想想用动态规划也没那么难,想通了理解了就还好
import java.util.*;
public class Main {
/* 此种方法超时*/
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int rooms = sc.nextInt();
int[] pi = new int[rooms];
for(int i=0 ; i<rooms ; i++) {
pi[i] = sc.nextInt();
}
int[] step = new int[rooms+1];
step[0] = 1;
int index = 0;
int count = 0;
while(index!=rooms) {
if(step[index]%2==0) {
index++;
} else {
//基于0索引,所以-1
index = pi[index]-1;
}
step[index]++;
step[index] %= 2;
count++;
count %= 1000000007;
}
System.out.println(count);
}
}
最后加上我的思路来源:https://blog.csdn.net/flushhip/article/details/79458502