题目链接
题意为统计满足“两两互质或两两不互质”的三元组的个数。首先暴力枚举肯定会超时。数据的规模暗示只能用 O(n) 的算法解决问题。这样以来用直接法统计似乎有点困难。于是看看间接法能不能帮我们减少麻烦。我们定义不满足条件的三元组为 Not(a,b,c) ,它是满足“有且仅有一个数对互质”或“有且仅有一个数对不互质”的三元组。这样, Not(a,b,c) 中一定有两个数满足“在 Not(a,b,c) ”中一个数与其互质,而另一个数与其不互质”。这样,我们就可以将问题变为“求对任意一个数a,有多少数与其不互质”(若将不互质改为互质的话复杂度将是 O(n2) )。设与 a 不互质的数的数量为 n(a) 。最终 Not(a,b,c) 的数量 ans=∑a(n(a)−1)(n−n(a))2 。为什么要除以 2 呢?因为前面提到, Not(a,b,c) 中有两个数满足与另外两个数分别互质与不互质。也就是说,求出来的 Not(a,b,c) 的数量有一半是重复的。现在,问题可以完全转化为如何求 n(a) 了。我们注意到,在 a 的范围内, a 的素因子的个数非常的少。那么我们可以在接近常数的时间内枚举a的素因子。例如,若 a=24 的话, a 的素因子为 2 和 3 。于是 n(a) 等于这些素因子的倍数的数量……等等,真的是这样的吗?这样算肯定是重复了。加入原数组中有 6 的话,那么 6 被 2 和 3 总共算了两遍。为了解决这样尴尬的局面,我们用专门对付这种重复的尴尬的容斥原理来解决。我们枚举素因子的组合,例如,若 a=24 的话, a 的素因子的组合为 2,3,6=2×3 。初始令 n(a)=0 ,当素因子的组合 m 中含有奇数个素因子的时候, n(a) 加上 mul(m) ( mul(m) 表示原始数组中 m 的倍数的个数, mul 数组可以在预处理中通过筛法线性地处理出来)。当素因子的组合 m 中含有偶数个素因子的时候, n(a) 减去 mul(m) 。当所有组合枚举完毕后 n(a) 就能求出来了。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 10, maxNum = 1e5 + 10;
int t, n, ub, a[maxn], cnt[maxn], mul[maxn];
ll num, ans, all;
vector <int> pf[maxn];
int main() {
// 预处理出各数的素因子
for(int i = 2; i < maxNum; i++) {
if(pf[i].size() == 0) {
for(int j = i; j < maxNum; j += i) {
pf[j].push_back(i);
}
}
}
scanf("%d", &t);
while(t--) {
scanf("%d", &n);
ub = 0;
memset(cnt, 0, sizeof(cnt));
for(int i = 0; i < n; i++) {
scanf("%d", &a[i]);
ub = max(ub, a[i]);
cnt[a[i]]++;
}
// 统计各数的倍数的个数
memset(mul, 0, sizeof(mul));
for(int i = 1; i <= ub; i++) {
for(int j = i; j <= ub; j += i) {
mul[i] += cnt[j];
}
}
ans = 0;
// 计算与a[i]不互素的a[i']的个数
for(int i = 0; i < n; i++) {
// 所有数都与1互素
if(a[i] == 1) {
continue;
}
int nf = pf[a[i]].size();
int nn = 1 << nf;
num = 0;
// 枚举素因子组合
for(int j = 1; j < nn; j++) {
// 累乘因子
int m = 1;
int flag = 0;
for(int k = 0; k < nf; k++) {
if((j >> k) & 1) {
m *= pf[a[i]][k];
flag++;
}
}
// 容斥原理,奇加偶减
// 累加与a[i]不互素的数的个数
num += (flag & 1 ? 1 : -1) * mul[m];
}
// 累加不符合条件的三元组的个数
// 有数与之不互素才能累加
if(num > 0) {
ans += (num - 1) * (n - num);
}
}
all = (ll)n * (n - 1) * (n - 2) / 6;
printf("%I64d\n", all - ans / 2);
}
return 0;
}
这题看似需要懂得一点物理知识,其实不用。把题意读透后发现这题实际上要我们求将 k 个行星移动后质心同行星的距离的平方和的最小值。枚举方案和枚举质心的复杂度都太高了,于是我们需要深挖题意。思考后发现,我们倾向于移动“外围”的 k 个行星,这样似乎能得到较小的平方和。例如,假设我们有 5 个行星从左到右依次编号为 1,2,3,4,5 ,若 k 为 3 的话我们倾向于移动 1,2,5 或 1,4,5 这些外围的行星。事实上,能够证明这样的贪心策略是有效的。现直接当它被证明过了,接下来这 k 个行星移动到哪里去呢?移动到将来(移动后)的质心上去会使平方和尽量地小。于是我们可以枚举连续的 n−k 个行星,令这些行星不移动,然后求出质心,最后直接求出这些行星到质心距离的平方和。到这里,算法已经很快了,但是n的规模较大,连续地求质心与平方和还是会使算法的复杂度太高。
对于质心坐标,若维护 n−k 个行星的坐标和 sum ,那么可求出质心
avg=sumn−k 。
对于平方和,设其为
ans=∑i(xi−avg)2 ,
展开上式得到
ans=∑ix2i−∑i2×xi×avg+∑iavg2 ,
令 squ=∑ix2i ,则有
ans=squ−sum2n−k 。
可见维护 sum 和 squ 就能高效地求出 ans 了。注意, sum 与 squ 的值会很大,因此为保证结果的精度应使用long long型变量,这样以来,我们必须保证程序不能提前出现浮点数。为此可以通过变形上式 (n−k)ans=(n−k)squ−sum2 来避免过早出现除法,从而避免过早出现浮点数。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e4 + 10;
int t, n, k;
ll a[maxn], sum, squ, res, ans;
int main() {
scanf("%d", &t);
while(t--) {
scanf("%d%d", &n, &k);
for(int i = 0; i < n; i++) {
scanf("%I64d", &a[i]);
}
if(n == k || n == k + 1) {
puts("0");
continue;
}
sort(a, a + n);
sum = squ = 0;
for(int i = 0; i < n - k; i++) {
sum += a[i];
squ += a[i] * a[i];
}
ans = (n - k) * squ - sum * sum;
for(int i = 0; i < k; i++) {
int j = i + n - k;
sum += (a[j] - a[i]);
squ += (a[j] * a[j] - a[i] * a[i]);
res = (n - k) * squ - sum * sum;
ans = min(ans, res);
}
printf("%.10f\n", (double)ans / (n - k));
}
return 0;
}
首先因为数据规模的原因搜索肯定是不行的了。其次这么复杂的元素之间的依赖关系意味着很难找到贪心策略。然后尝试动态规划。由于这是线性的结构,所以考虑前 n 个音符的最优解。我们可以规定 d[i] 为前 i 个(包括第 i 个)音符确定后产生的最优解。现在我们尝试用 d[i],d[i−1],...d[1] 这些信息来确定 d[i+1] 。很遗憾,我们无法仅通过这么少的信息推出 d[i+1] 。那么缺少什么信息呢?经过思考后发现,只要知道第i个音符是什么音符就能得到 d[i+1] 了。现在重新规定 d[i][j] 为前 i 个(包括第 i 个)音符确定后,第 i 个音符为 j ,这种情况(状态)下的最优解。至此就能得到状态转移方程: d[i][j]=max{d[i−1][k]+v[k][j],1≤k≤m} ,其中 v 为确定美丽值的矩阵。另外,因为这题中搜索解会很方便(只是复杂度太高),所以用记忆化搜索来代替动态规划也是不错的选择。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 105, maxm = 55;
int t, n, m, a[maxn], v[maxm][maxm], d[maxn][maxm];
// 记忆化搜索
int dfs(int cur, int num) {
if(cur > n) return 0;
int res = 0;
if(a[cur] > 0) {
res = v[num][a[cur]] + dfs(cur + 1, a[cur]);
}
else {
// 尝试向第cur位中填入各种音符
for(int j = 1; j <= m; j++) {
int tmp = d[cur+1][j] ? d[cur+1][j] : dfs(cur + 1, j);
res = max(res, v[num][j] + tmp);
}
}
// 保存最优子结构的最优解
return d[cur][num] = res;
}
int main() {
scanf("%d", &t);
while(t--) {
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i++) {
for(int j = 1; j <= m; j++) {
scanf("%d", &v[i][j]);
}
}
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
memset(d, 0, sizeof(d));
printf("%d\n", dfs(1, 0));
}
return 0;
}
简单实现题。按照题意实现即可。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1010;
int kase, n;
double d, ans;
double t[maxn], x[maxn], y[maxn];
int main() {
scanf("%d", &kase);
while(kase--) {
scanf("%d", &n);
for(int i = 0; i < n; i++) {
scanf("%lf%lf%lf", &t[i], &x[i], &y[i]);
}
ans = 0;
for(int i = 1; i < n; i++) {
d = hypot(x[i] - x[i-1], y[i] - y[i-1]);
ans = max(ans, d / (t[i] - t[i-1]));
}
printf("%.10f\n", ans);
}
return 0;
}
(其他题目略)