【解题报告】2014ACM/ICPC亚洲区鞍山站

题目链接

C.Coprime(HDU5072)

思路

题意为统计满足“两两互质或两两不互质”的三元组的个数。首先暴力枚举肯定会超时。数据的规模暗示只能用 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)(nn(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;
}

D.Galaxy(HDU5073)

思路

这题看似需要懂得一点物理知识,其实不用。把题意读透后发现这题实际上要我们求将 k 个行星移动后质心同行星的距离的平方和的最小值。枚举方案和枚举质心的复杂度都太高了,于是我们需要深挖题意。思考后发现,我们倾向于移动“外围”的 k 个行星,这样似乎能得到较小的平方和。例如,假设我们有 5 个行星从左到右依次编号为 12345 ,若 k 3 的话我们倾向于移动 125 145 这些外围的行星。事实上,能够证明这样的贪心策略是有效的。现直接当它被证明过了,接下来这 k 个行星移动到哪里去呢?移动到将来(移动后)的质心上去会使平方和尽量地小。于是我们可以枚举连续的 nk 个行星,令这些行星不移动,然后求出质心,最后直接求出这些行星到质心距离的平方和。到这里,算法已经很快了,但是n的规模较大,连续地求质心与平方和还是会使算法的复杂度太高。

对于质心坐标,若维护 nk 个行星的坐标和 sum ,那么可求出质心

avg=sumnk

对于平方和,设其为

ans=i(xiavg)2

展开上式得到

ans=ix2ii2×xi×avg+iavg2

squ=ix2i ,则有

ans=squsum2nk

可见维护 sum squ 就能高效地求出 ans 了。注意, sum squ 的值会很大,因此为保证结果的精度应使用long long型变量,这样以来,我们必须保证程序不能提前出现浮点数。为此可以通过变形上式 (nk)ans=(nk)squsum2 来避免过早出现除法,从而避免过早出现浮点数。

代码

#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;
}

E.Hatsune Miku(HDU5074)

思路

首先因为数据规模的原因搜索肯定是不行的了。其次这么复杂的元素之间的依赖关系意味着很难找到贪心策略。然后尝试动态规划。由于这是线性的结构,所以考虑前 n 个音符的最优解。我们可以规定 d[i] 为前 i 个(包括第 i 个)音符确定后产生的最优解。现在我们尝试用 d[i],d[i1],...d[1] 这些信息来确定 d[i+1] 。很遗憾,我们无法仅通过这么少的信息推出 d[i+1] 。那么缺少什么信息呢?经过思考后发现,只要知道第i个音符是什么音符就能得到 d[i+1] 了。现在重新规定 d[i][j] 为前 i 个(包括第 i 个)音符确定后,第 i 个音符为 j ,这种情况(状态)下的最优解。至此就能得到状态转移方程: d[i][j]=max{d[i1][k]+v[k][j],1km} ,其中 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;
}

I.Osu!(HDU5078)

思路

简单实现题。按照题意实现即可。

代码

#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;
}

(其他题目略)

你可能感兴趣的:(解题报告,2014,ACM-ICPC,鞍山站,亚洲区)