总结计蒜客视频习题,附题目描述和解题思路及代码,持续更新…
文章目录
1. 蒜头君爬楼梯2
题目描述
蒜头君很喜欢爬楼梯,这一次,他获得了一个特异功能,每次可以跳跃任意奇数的阶梯。比如他初始在楼底,跨越一个阶梯到达1号阶梯,或者跨越3个楼梯到达3号阶梯。为了选出一种最轻松的爬楼梯的方式,蒜头君想把所有不同的到达楼顶的方式都尝试一遍。对于-共有n个阶梯的楼梯,蒜头君一共有多少总方法从楼底到达楼顶。由于最后答案可能很大,输出最后的答案对100007取模的结果。
解题思路
利用动态规划,因为可以跳跃奇数,可按照奇数进行遍历,如初始位置 i ,则上一步可能为i - 1 ,i - 3, i-5, …
#include<bits/stdc++.h>
using namespace std;
const int mod=100007;
int a[10101];
int main()
{
int n;
cin>>n;
a[0]=1;
for(int i=1; i<=n; i++)
{
a[i]=0;
for(int j=i-1; j>=0; j-=2)
/*j初始设为i-1!!!不能设初始j为i
*(动态规划:考虑从上一步开始)并在每次循环-2,可保证每次是按照1 3 5 7 9 ...递减循环
*逆序处理才能够遍历小于本台阶的加上奇数为这个数的数
*/
{
a[i]+=a[j];
a[i]%=mod;
}
}
cout<<a[n]<<endl;
return 0;
}
2. 弹簧板(加强)
题目描述
有一个小球掉落在一串连续的弹簧板上,小球落到某-个弹簧板后, 会被弹到某一 个地点, 直到小被弹到弹簧板以外的地方。假设有n个连续的弹簧板,每个弹簧板占- -个单位距离, a[i]代表第i个弹簧板会把小球向前弹a[i]个距离。比如位置1的弹簧能让小球前进2个距离到达位置3。如果小球落到某个弹簧板后,经过一系列弹跳会被弹出弹簧板,那么小球就能从这个弹簧板弹出来。现在希望你计算出小球从任意一个弹簧板落下 ,最多会被弹多少次后,才会弹出弹簧板。
输入格式
第一个行输入一个n代表-共有n个弹簧板。第二行输入n个数字,中间用空格分开。第i个数字a[]代表第i个弹簧板可以让小球移动的距离。
数据约定:
对于50%的数据:1≤n≤1000,0< a[i]≤30。
对于100%的数据:1≤n≤10000,0< a[i] < 30。
输出格式
输出一个整数,代表小球最多经过多少次才能弹出弹簧板。|
样例输入
5
2 2 3 1 2
样例输出
3
解题思路
从i位置可以跳到a[i]+i的位置,最后一个肯定是只需要跳一次就跳出去,那么
从i位置跳出去的次数就是从a[i]+i跳出的次数加上他这次跳的一次
从最后一个弹簧板倒着往前推,用dp[i]数组记录下在i处下落的弹簧多少次可以出去,这样dp[i] = 1 + dp[i+a[i]] ;
i+a[i] 可得到下标,到达最后一个弹簧板后还需要再弹跳一次,故 +1
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int a[100110],dp[100110];
memset(dp, 0, sizeof(dp)); //必须要有这一步
int n;
int res = 0;
int main() {
scanf("%d",&n);
for(int i = 1;i <= n;i++) {
scanf("%d",&a[i]);
}
for(int i = n;i >= 1;i--) { //从n开始递推
dp[i] = 1 + dp[i+a[i]]; //dp[i+a[i]] 表示能够弹到的位置 1 表示这次我去弹的次数
res = max(res,dp[i]);
}
printf("%d\n",res);
return 0;
}
3. 蒜头君的新游戏
问题描述
工作空闲之余,蒜头君经常带着同事们做游戏,最近蒜头君发明了一个好玩的新游戏:n 位同事围成一个圈,同事 A 手里拿着一个兔妮妮的娃娃。蒜头君喊游戏开始,每位手里拿着娃娃的同事可以选择将娃娃传给左边或者右边的同学,当蒜头君喊游戏结束时,停止传娃娃。此时手里拿着娃娃的同事即是败者。 玩了几轮之后,蒜头君想到一个问题:有多少种不同的方法,使得从同事 A 开始传娃娃,传了 m 次之后又回到了同事 A 手里。两种方法,如果接娃娃的同事不同,或者接娃娃的顺序不同均视为不同的方法。例如1−>2−>3−>1 和 1->3->2->1 是两种不同的方法。
输入格式
输入一行,输入两个整数n,m(3≤n≤30,1≤m≤30),表示一共有 n 位同事一起游戏,一共传 m 次娃娃。
输出格式
输出一行,输出一个整数,表示一共有多少种不同的传娃娃方法。
样例输入
3 3
样例输出
2
解题思路
1.定义数组:dp[i][j]–传了i次,现在在j手上
2.边界条件:
传到1号手里,可由2号和n号传递给1号,dp[i][j]=dp[i-1][2]+dp[i-1][n];
传到n号手里,可由1号和n-1号传递给n号,dp[i][j]=dp[i-1][1]+dp[i-1][n-1];
3.状态方程:dp[i][j]=dp[i-1][j-1]+dp[i-1][j+1];
int main(){
int n,m;
int dp[35][35]; //dp[i][j]传了i次,现在在j手上
cin>>n>>m;
memset(dp,0,sizeof(dp));
dp[0][1]=1;
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(j==1){
dp[i][j]=dp[i-1][2]+dp[i-1][n];
}else if(j==n){
dp[i][j]=dp[i-1][1]+dp[i-1][n-1];
}else{
dp[i][j]=dp[i-1][j-1]+dp[i-1][j+1];
}
}
}
cout<<dp[m][1]<<endl;
return 0;
}
4. 计蒜客习题:逃生
题目描述
输入格式
第一行依次输入整数 n,m,x,y,v,c(1<n,m≤1000,1≤x≤n,1≤y≤m,1≤v≤c≤10000), 其中 n,m 代表地图大小,(x,y) 代表蒜头君的初始位置,v 代表蒜头的初始化血量,c 代表蒜头的生命值上限。接下来 n 行,每行有 m 个数字,代表地图信息。(每个数字的绝对值不大于100,地图中蒜头君的初始位置的值一定为 0)
输出格式
一行输出一个数字,代表成功逃生最多剩余的血量,如果失败输出 −1。
样例输入
4 4
3 2 5 10
1 2 3 4
-1 -2 -3 -4
4 0 2 1
-4 -3 -2 -1
样例输出
10
解题思路
#include<iostream>
#include<math.h>
#include<algorithm>
using namespace std;
//给了四个方向,我不知道从哪一个方向走,那我定义四个方向数组,求出往四个方向走的最大值
const int INF = -3356;
const int maxn = 1010;
int a[maxn][maxn];
int dp[maxn][maxn];
int main(){
int n, m, x, y, v, c; //n行m列x,y初始位置,v初始血量,c最大血量
cin >> n >> m >> x >> y >> v >> c;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> a[i][j];
}
}
//定义四个方向!!!!!!
int xx[4] = { -1,-1,1,1 };
int yy[4] = { -1,1,-1,1 };
for (int t = 0; t < 4; t++) {
for (int i = x; i > 0 && i <= n; i -= xx[t]) {
for (int j = y; j > 0 && j <= m; j -= yy[t]) {
if (i == x && j == y) {
dp[i][j] = v;
}
else if (i == x) { //在同一行,可推断是在左右方向走
dp[i][j] = min(c, dp[i][j + yy[t]] + a[i][j]);
}
else if (j == y){ //在同一列,在上下方向走
dp[i][j] = min(c, dp[i + xx[t]][j] + a[i][j]);
}
else{
dp[i][j] = min(c, max(dp[i + xx[t]][j], dp[i][j + yy[t]]) + a[i][j]);
}
if (dp[i][j] <= 0) {
dp[i][j] = -INF;
}
}
}
}
int ans = max(max(dp[1][1], dp[1][m]), max(dp[n][1], dp[n][m])); //利用max函数求出四个出口的最大值
if (ans <= 0) {
cout << "-1" << endl;
}else{
cout << ans << endl;
}
return 0;
}
5. 一维消消乐
题目描述
有 n 颗珠子排成一排,每一颗珠子有一个价值 wi(可能是负数)。游戏是这样,你可以选择如若干对相邻的珠子,让他们同时消去。每一对珠子的消失,都会使得总分数加上两颗珠子相乘的分数。注意,每个珠子只能消一次,并且珠子消去以后,还会占位。
输入格式
输入第一行一个整数n(1≤n≤10000)。
接下来一行输入 n 个整数 (−1000≤ wi ≤1000)。
输出格式
输出最大的分数。
样例输入:
8
-9 -5 -4 -2 4 -5 -4 2
样例输出:
73
const int maxn=10010;
int dp[maxn][2] //1代表和前面组合 0代表没有和前面组合
int w[maxn]
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>w[i];
}
dp[1][0]=0;
for(int i=2;i<=n;++i){
dp[i][0]=max(dp[i-1][0],dp[i-1][1]);
dp[i][1]=dp[i-1][0]+w[i]*w[i-1];
}
cout<<max(dp[n][0],dp[n][1])<<endl;
return 0;
}
6. 数组分组
题目描述
一个长度为n的数组a,我们可以把它分成任意组,每一组是一 段连续的区间。比如数组1,2, 3,4,5可以
分成(1,2), (3, 4, 5)两个组。每个分组都有一个权值,这个权值就是分组里面每个数的乘积对1000取模的结果。对于数组a的一个分组方案,总权值就是每个分组的权值和。那么对于数组a,分组以后最大的权值和是多少?
输入格式.
输入第一张一个整数n(1≤n≤1000)。接下来一行n个整数,表示数组a,数组中每个元素都小于等于100。
输出格式
数组最大的分组权值和。
样例输入
7
52 26 1 36 72 48 43
样例输出
1596
//1、预处理过程,2、如何比较找出最大权值和
const int maxn=1010;
const int mod=1000;
int pre[maxn][maxn];
int a[maxn];
int dp[maxn];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){ //预处理,每一段分组造成的权值和
pre[i][i]=a[i];
for(int j=i+1;j<=n;++j){
pre[i][j]=pre[i][j-1]*a[j]%mod; //pre[i][j]代表区间i到j所有数的乘积
}
}
dp[0]=0; //dp[i]代表考虑到第i个数时的最大和
dp[1]=a[1];
for(int i=2;i<=n;++i){
for(int j=0;j<i;++j){
//求出最大值
dp[i]=max(dp[i],dp[j]+pre[j+1][i]); //dp[j]+pre[j+1][i]前j位划分成一组,再从j+1到i位置划分成一组
}
}
cout<<dp[n]<<endl;
}
7. 最大子段和
题目描述
输出一段数字,求数字串中连续子和的段最大值
输入
6
-1 11 -4 13 -5 -6
输出
20
typedef inf 9999;
int main(){
int n,num[105];
cin>>n;
for(int i=0;i<n;i++){
cin>>num[i];
}
int ans=-inf;
for(int i=0;i<n;i++){
ans=max(ans,num[i]);
}
//求ans是考虑全部数字为负数情况
if(ans<=0){
cout<<ans<<endl;
}else{
int sum=0;
for(int i=0;i<n;i++){
//一定要去判断sum+num[i]是否小于0,若小于0,则舍弃负数子段,使sum=0
if(sum+num[i]<0){
sum=0;
}else{
sum+=num[i];
}
//一定要求max,max里面的ans保留了上一次球取的最大值,
ans=max(ans,sum);//
}
}
cout<<ans<<endl;
return 0;
}
**
8. 最大矩阵和
题目描述
解题思路
这道题要求求最大子矩阵之和,这道题其实和最大子段和很像。首先我们用前缀和求出每一列上前i行的和,之后,我们先要把行的上界和下界固定下来,然和把每一列的数都加起来。想象一下其实就是相当于把一个矩阵压缩成一列数,然后用求最大子段和的方法求出最大值。随后,我们需要尝试每一种可能,也就是每一种上下界不同的情况下的最大矩阵和,找出最大的输出即可。
代码
long long num[401][401], presum[401][401];
int main() {
int N, M;
long long sum, ans;
cin >> N >> M;
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= M; j++) {
cin >> num[i][j];
ans = max(ans, num[i][j]);
}
}
if (ans <= 0) { //num最大值都小于等于0,直接返回ans
cout << ans << endl;
}
else {
for (int i = 1; i <= N; i++) { //预处理
for (int j = i; j <= M; j++) {
presum[i][j] = presum[i - 1][j] + num[i][j];//第i行j列的前缀和
}
}
for (int i = 1; i <= N; i++) {//i为矩阵上边界
for (int j = i; j <= N; j++) {//j为矩阵下边界
sum = 0;
for (int k = 1; k <= M; k++) {//一维最大子段和,利用前缀和快速计算
if (sum + presum[j][k] - presum[i - 1][k] < 0) {
sum = 0;
}
else {
sum += presum[j][k] - presum[i - 1][k];
}
ans = max(ans, sum);
}
}
}
cout << ans << endl;
}
return 0;
}
9. 墙壁涂色
问题描述
输入格式
一个整数 n,表示房间被划分成多少部分。(1≤n≤50)
输出格式
一个整数,表示给墙壁涂色的合法方案数。
样例输入
4
样例输出
18
解题思路
第一个和第n-1个不同,则第n个需要和这两个再不同,第n位置只有一种选择a[i-1]
第一个和第n-1个相同,则第n-2个要和第一个不同, 第n项有两种颜色可以取,同时因为第n块和第n-1块不同色,所以a[n]=2*a[n-2]
typedef long long ll;
int main(){
int n;
ll a[51];
a[1]=3;
a[2]=6;
a[3]=6;
for(int i=4;i<=50;i++){
a[i]=a[i-1]+a[i-2]*2;
}
cin>>n;
cout<<a[n]<<endl;
return 0;
}
/*第一个和第n-1个不同,则第n个需要和这两个再不同,第n位置只有一种选择a[i-1]
第一个和第n-1个相同,则第n-2个要和第一个不同, 第n项有两种颜色可以取,同时因为第n块和第n-1块不同色,所以a[n]=2*a[n-2]
*/
10. 小朋友过桥
题目描述
输入:
两行数据:第一行为小朋友个数n,第二行有n个数,用空格隔开,分别是每个小朋友过桥的时间。
输出:
一行数据:所有小朋友过桥花费的最少时间。
样例输入
4
1 2 5 10
输出
17
解题思路:
我们先将所有人按花费时间递增进行排序,假设前i个人过河花费的最少时间为opt[i],那么考虑前i-1个人过河的情况,即河这边还有1个人,河那边有i-1个人,并且这时候手电筒肯定在对岸,
所以opt[i] = opt[i-1] + a[1] + a[i] (让花费时间最少的人把手电筒送过来,然后和第i个人一起过河)
如果河这边还有两个人,一个是第i号,另外一个无所谓,河那边有i-2个人,并且手电筒肯定在对岸,所以opt[i] = opt[i-2] + a[1] + a[i] + 2a[2] (让花费时间最少的人把电筒送过来,然后第i个人和另外一个人一起过河,由于花费时间最少的人在这边,所以下一次送手电筒过来的一定是花费次少的,送过来后花费最少的和花费次少的一起过河,解决问题)
所以 opt[i] = min{opt[i-1] + a[1] + a[i] , opt[i-2] + a[1] + a[i] + 2a[2] }。
来看一组数据 四个人过桥花费的时间分别为 1 2 5 10
具体步骤是这样的:
第一步:1和2过去,花费时间2,然后1回来(花费时间1);
第二歩:3和4过去,花费时间10,然后2回来(花费时间2);
第三部:1和2过去,花费时间2,总耗时17。
int a[1010];
int f[1010]; //前多少个人过去了,且手电筒在对岸
int main(){
int n;
cin>>n;
for(int i=0;i<n;i++){
cin>>a[i];
}
sort(a,a+n);
f[0]=a[0];
f[1]=a[1];
for(int i=2;i<n;i++){
f[i]=min(f[i-1]+a[0]+a[i],f[i-2]+a[0]+2*a[1]+a[i]);
//0号回来接a[i],让0号带着i一起走
}
cout<<f[n-1]<<endl;
return 0;
}
**
11. 最长上升子序列(LIS)
**
字符子串:字符串中连续的n个字符,如abcdefg中,ab,cde,fg等都属于它的字串。
字符子序列:字符串中不一定连续但先后顺序一致的n个字符,即可以去掉字符串中的部分字符,
但不可改变其前后顺序。如abcdefg中,acdg,bdf属于它的子序列,而bac,dbfg则不是
测试用例
输入
6
3 2 6 1 4 5
输出
3
代码
int dp[110],a[110],n,ans=0;
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
dp[i]=1;
for(int j=1;j<i;j++){
if(a[j]<a[i])
dp[i]=max(dp[i],dp[j]+1); //不断在j<i的情况下,对dp[i]取最大值
}
ans=max(ans,dp[i]); //在i不同值的情况下,求dp[i]最大值
}
cout<<ans<<endl;
return 0;
}
**
12. 最长公共子序列(LCS)
**
代码:
int dp[110][110];
int main() {
string a, b;
memset(dp, 0, sizeof(dp));
cin >> a >> b;
int lena = a.size();
int lenb = b.size();
for (int i = 1; i <= lena; i++) {
for (int j = 1; j <= lenb; j++) {
if (a[i - 1] == b[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
}
else {
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
cout << dp[lena][lenb] << endl;
return 0;
}
13. 编辑距离
解题思路:
首先我们想到的第一种办法是搜索, 因为我们需要找到的是“最少”的操作次数,因此当S的长度为m, T的长度为n时,我们可以找到操作次数的界限:
(1)把T中的字符全删了,再添加S的全部字符,操作次数 m + n;
(2)把T中的字符长度变成m,再修改,操作次数最多 |m- n| + m.
但是我们发现,搜索的方式并不可行。因为搜索的空间是指数级的,这取决于S中字符数量的种类一如果 S是一个由小写字母组成的字符串,那么它的复杂度是O(26m),再思考一下,这个问题之所以难,是因为这个问题当中有诸如“添加”、“删除” 这些操作。我们不妨换一种理解方式,从字符串对齐的角度来理解这个问题。给定字符串S和T,我们要通过往这两个字符串当中加入一种特殊符号(这里用 - 代替), 来让这两个字符串的长度相同并且“对齐”,如果两个串相同的位置出现了不同的字符,那么就扣1分,我们要让两个字符串对齐的扣分尽量少。对于例子我们实际上采用了这样的对齐方式:
1 2 3 4 5
S A B C F -
T D B - F G
那么我们看一下:
(1) S和T中的字符都为普通字符且相同(位置2,4)一不扣分。
(2)S和T中的字符都为普通字符但不同(位置1)一扣1分。
(3)S中为普通字符,T中为’-'(位置3)一扣1分。
(4)T中为普通字符,S中为‘-’(位置5)一扣1 分。
我们来看看前面的项目对应什么:
(1)直接对应;
(2)对应在T中修改该字符:
(3)对应在T中添加该字符;
(4)对应在T中删除该字符。
好了,感觉是不是和前面的LCS很像?令f(i, j)表示S的前i位和T的前j位对齐后的最少扣分,我们尝试看看四种情况:
(1)此时很明显S[i] = T[j],表示当前位置是对齐的,因此答案就是 f(i - 1,j - 1)。
(2)此时和(1)中类似,只不过S[i]≠T[j],那么在当前位置需要修改,因此答案就是 f(i - 1,j - 1) +1.
(3) S的前i位和T的前j - 1位对齐之后,在当前位置需要执行一次添加操作。 因此此时的答案应为 f(i,j - 1) + 1
(4)S的前i - 1位和T的前 j 位对齐之后,在当前位置需要执行一次删除操作。 因此此时的答案应为 f(i - 1,j) + 1
因此,我们可以推出递推式:
对于S的前0个字符,我们只能把T中的字符全部删掉。同样道理,对于T中的前0个字符,我们只能选择把T中
添加上S当前长度的字符。
代码:
int dp[110][110];
int main() {
string a, b;
memset(dp, 0, sizeof(dp));
cin >> a >> b;
int lena = a.size();
int lenb = b.size();
for (int i = 1; i <= lena; i++) {
dp[i][0] = i;
}
for (int i = 1; i <= lenb; i++) {
dp[0][i] = i;
}
for (int i = 1; i <= lena; i++) {
for (int j = 1; j <= lenb; j++) {
if (a[i - 1] == b[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
}
else {
dp[i][j] = min(min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
}
}
}
cout << dp[lena][lenb] << endl;
return 0;
}
14. 蒜头君的最大子矩阵和
解题思路:
这道题要求求最大子矩阵之和,这道题其实和最大子段和很像。首先我们用前缀和求出每一列上前i行的和,之后,我们先要把行的上界和下界固定下来,然和把每一列的数都加起来。想象一下其实就是相当于把一个矩阵压缩成一列数,然后用求最大子段和的方法求出最大值。随后,我们需要尝试每一种可能,也就是每一种上下界不同的情况下的最大矩阵和,找出最大的输出即可。
代码:
long long num[401][401], presum[401][401];
int main() {
int N, M;
long long sum, ans;
cin >> N >> M;
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= M; j++) {
cin >> num[i][j];
ans = max(ans, num[i][j]);
}
}
if (ans <= 0) { //num最大值都小于等于0,直接返回ans
cout << ans << endl;
}
else {
for (int i = 1; i <= N; i++) { //预处理
for (int j = i; j <= M; j++) {
presum[i][j] = presum[i - 1][j] + num[i][j];//第j列前i行总和
}
}
for (int i = 1; i <= N; i++) {//i为矩阵上边界
for (int j = i; j <= N; j++) {//j为矩阵下边界
sum = 0;
for (int k = 1; k <= M; k++) {//一维最大子段和,利用前缀和快速计算
if (sum + presum[j][k] - presum[i - 1][k] < 0) {
sum = 0;
}
else {
sum += presum[j][k] - presum[i - 1][k];
}
ans = max(ans, sum);
}
}
}
cout << ans << endl;
}
return 0;
}
15. 跳木桩
解题思路:
求最长不递增子序列
参考最长递增子序列, 从后往前找
代码:
int a[1010], dp[1010];
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> a[i];
}
int ans = 0;
for (int i = 0; i < n; i++) {
dp[i] = 1;
for (int j = 0; j < i; j++) {
if (a[j] >= a[i]) {//从后往前找不递增数
dp[i] = max(dp[i], dp[j] + 1);
}
}
ans = max(ans, dp[i]);
}
cout << ans << endl;
return 0;
}
16. 删除最少的元素
解题思路:
题目要求的数是先递减后递增
递减——最长不增子序列
递增——最长递增子序列
首先从前往后找出每一个位上的最长非递增子序列,然后从后往前找出每一位上的最长非递增子序列,
最后把前后位上的结果相加-1,因为要找出删除最少的元素数,所以 n - ans 即可
代码:
int a[1010], dp[2][1010];//i为0,正序求最长非递增子序列,1逆序求最长非递增子序列
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> a[i];
}
int ans = 0;
for (int i = 0; i < n; i++) {
dp[0][i] = 1;
for (int j = 0; j < i; j++) {
if (a[j] >= a[i]) { //正序
dp[0][i] = max(dp[0][i], dp[0][j] + 1);
}
}
}
for (int i = n - 1; i >= 0; i--) {
dp[1][i] = 1;
for (int j = n - 1; j > i; j--) {
if (a[j] >= a[i]) { //倒序
dp[1][i] = max(dp[1][i], dp[1][j] + 1);
}
}
}
for (int i = 0; i < n; i++) {
ans = max(ans, dp[0][i] + dp[1][i] - 1);
}
cout << n - ans << endl;
return 0;
}