CF892C Pride 题解 区间DP
题目链接:http://codeforces.com/contest/892/problem/C
CF原版题解题解链接:http://codeforces.com/blog/entry/55841
题目描述(人名略有改变)
灵灵和聪聪在玩一个叫做“相约九八”的游戏。
一开始给他们一个数组 \(a_1,a_2, \cdots ,a_n\) ,
然后每次操作他们可以选择数组中相邻的两个数,
我们假设这两个数的数值分别为 \(x\) 和 \(y\),
我们可以求出他们的最大公约数 \(gcd(x,y)\) ,然后将其中一个数的数值变为 \(gcd(x,y)\) 。
请你帮他们计算一下,他们最少需要几步能够将数组中的所有元素都变为 \(1\) 。
输入格式
输入的第一行包含一个整数 \(n\) ( \(1 \le n \le 2000\) )——表示数组中元素的个数。
输入的第二行包含 \(n\) 个整数:\(a_1,a_2, \cdots ,a_n\) ( \(1 \le ai \le 10^9\) ),用于表示数组中的每个元素。
输出格式
如果没有办法将数组中的所有元素都变成 \(1\) ,则输出 \(-1\) ;否则输出将数组中的所有元素都变为 \(1\) 的最少步数。
样例输入1
5
2 2 3 4 6
样例输出1
5
样例输入2
4
2 4 6 8
样例输出2
-1
样例输出3
3
2 6 9
样例输出3
4
【样例解释】
对于样例1,我们可以使用如下 \(5\) 步将数组中的所有元素都转换成 \(1\) :
- \([2, 2, 3, 4, 6]\)
- \([2, 1, 3, 4, 6]\)
- \([2, 1, 3, 1, 6]\)
- \([2, 1, 1, 1, 6]\)
- \([1, 1, 1, 1, 6]\)
- \([1, 1, 1, 1, 1]\)
题目分析
本题设计内容:区间DP。
我们设 \(cnt1\) 为 \(a\) 中元素 \(1\) 的个数。
如果 \(0 < cnt1\) ,那么答案就是 \(n - cnt1\) 。
否则我们需要找到数组 \(a\) 中 \(gcd\) (这里 \(gcd\) 表示最大公约数)等于 \(1\) 的最短的连续子串。
我们定义数组中从坐标 \(L\) 开始到坐标 \(R\) 结束的这段区间内的所有元素的 \(gcd\) 为 \(dp[L][R]\) 。
可以得到状态转移方程为
- \(dp[i][i] = a[i]\)
- \(dp[i][j] = gcd(dp[i][j-1], a[j])\)
如果 \(dp[1][n] \ne 1\) ,则直接输出 \(-1\) 。
否则,我们假设通过 \(dp\) 数组求得了最短的长度 \(mlen\) (表示最少有 \(mlen\) 个相邻元素它们的 \(gcd\) 等于 \(1\) ),
那么答案就是 \(mlen-1 + n-1\) 。
实现代码如下:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2020;
int n, a[maxn], dp[maxn][maxn], cnt1;
int get_min_len() {
for (int i = 0; i < n; i ++) dp[i][i] = a[i];
for (int l = 1; l <= n; l ++) {
for (int i = 0; i+l-1 < n; i ++) {
int j = i + l - 1;
dp[i][j] = __gcd(dp[i][j-1], a[j]);
if (dp[i][j] == 1) return l;
}
}
return -1;
}
int main() {
cin >> n;
for (int i = 0; i < n; i ++) {
cin >> a[i];
if (a[i] == 1) cnt1 ++;
}
if (cnt1 > 0) {
cout << n - cnt1 << endl;
return 0;
}
int mlen = get_min_len();
if (mlen == -1) {
cout << -1 << endl;
} else {
cout << mlen-1 + n-1 << endl;
}
return 0;
}