关于dtoj 177 谐振之石的一些反思
题目如下:
谐振之石 - 题目详情 - 岱陌存理 (dtoj.team)
这题我没有出,先粘贴上题解代码。
// #include <bits/stdc++.h>// using namespace std;
// using ll = long long;
#include <bits/stdc++.h>
using namespace std;
using ll = long long;// 求最大公约数
// int g(int a, int b) {
// while (b) {
// a %= b;
// swap(a, b);
// }
// return a;
// }
int g(int a,int b){while(b){a%= b;swap(a,b);}return a;
}// void slv() {
// int n;
// cin >> n;// vector<pair<int, int>> dp; // 存储 (GCD值, 区块右端点位置)
// ll ans = 0;
void slv(){int n ;cin>>n;vector<pair<int,int>> dp;ll ans = 0;// for (int i = 1; i <= n; ++i) {
// int x;
// cin >> x;// vector<pair<int, int>> cur;
// cur.push_back({x, i});
for(int i = 1;i <= n;i ++){int x;cin>>x;vector<pair<int,int>> cur;cur.push_back({x,i});// 基于i-1的结果更新
// for (auto const& [v, p] : dp) {
// int ngd = g(v, x);
// // 如果新的GCD与当前区块的GCD不同, 创建新区块
// if (ngd != cur.back().first) {
// cur.push_back({ngd, p});
// }
// }for (auto const& [v,p]:dp){int ngd = g(v,x);if (ngd != cur.back().first){cur.push_back({ngd,p});}}// dp = cur;// int lps = 0; // 上一个区块的右端点位置
// // dp中位置是降序的, 反向遍历计算贡献dp = cur;int lps = 0;
// for (int j = dp.size() - 1; j >= 0; --j) {
// auto const& [v, p] = dp[j];
// if (v > 1) {
// ans += (p - lps); // 区块长度为 p - lps
// }
// lps = p;
// }
// }
for (int j = dp.size() - 1;j >= 0 ; j --){auto const& [v,p] = dp[j];if (v > 1){ans += (p - lps);}lps = p;
}
}// cout << ans << endl;
// }
cout << ans<<endl;
}// int main() {
// ios::sync_with_stdio(0);
// cin.tie(0);
// cout.tie(0);// slv();// return 0;
// }
int main(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);slv();return 0;
}
这题用了一种比较灵活的dp方式,dp数组维护了表示枚举到j时,最大公因数能得到i,先从左到右进行类似于选与不选的gcd运算,可以把当前元素作为右端点加入cur数组,而加入方式分为两种:
1.最大公因数在原来数组中存在,那么此段数组能通过后面的枚举合并计算。
2.最大公因数在原来数组中不存在,那么此段数组并不能通过端点合并的方式进行计算,那么只能重新存一个最大公因数和右端点进入dp数组了。
把dp数组换成cur数组,把继承得到的所有【gcd,右端点】中gcd>1的加入答案。
感觉就是,通过合并gcd相同的子数组,从而实现了对gcd=1的子数组的剪枝,然后易得gcd运算是log的 所以时间复杂度是
(求gcd和遍历了一个gcd数组构成了log^2