题目链接: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]
e 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;
}