TC SRM668 DIV 2解题报告

前言

抽完奖留45分钟打TC,刺激…


T1 VerySecureEncryption

题意

给你一个字符串和一组键值,要求你一次操作将原串按键值映射成新串。求K次操作后的串。

分析

按题意模拟即可

贺代码

就这么短

string VerySecureEncryption::encrypt(string message, vector <int> key, int K) {
    int len = message.size(), i;
    string org;
    while (K--) {
        for (i = 0, org = message; i < len; i++) message[key[i]] = org[i];
    }
    return message;
}

T2 IsItASquare

题意

给出四个点的坐标,判断它们是否能构成一个正方形。

分析

数学题呜呜…蒟蒻表示不会(旁边chy_dalao有极角排序的做法)
于是我打了一个暴力,枚举两个点的连线作为对角线,若另外两个点每个点与对角线的连线都构成直角,且这四条直线长度相等,那么能构成正方形。

证明

首先,若某个点与对角线两端点连线不是直角,那么一定不成正方形。
然后,如果这两个角都为直角的话,那么就可以把另外两个点当做是在以当前枚举的对角线为直径的圆上运动。这时候若要是正方形的话,对角线和两边的两个点都会构成等腰直角三角形,证毕。

贺代码

//tc is healthy, just do it
#include <bits/stdc++.h>
using namespace std;
const double EPS = 1e-5;

class IsItASquare {
public:
    string isSquare( vector <int> x, vector <int> y );
};

// check判断连线是否垂直,用了一个简单的k1 * k2 == -1
inline bool check(double x1, double y1, double x2, double y2, double x3, double y3) {
    return (y1 - y2) * (y1 - y3) == (x2 - x1) * (x1 - x3);
}
inline double sqr(double x) { return x * x; }
inline double dist(double x1, double y1, double x2, double y2) {
    return sqrt(sqr(x1 - x2) + sqr(y1 - y2));
}

string IsItASquare::isSquare(vector <int> x, vector <int> y) {
    int i, j, k;
    bool f;
    for (i = 0; i < 3; i++)
        for (j = i + 1; j < 4; j++) {
            for (k = 0, f = true; k < 4; k++)
                if (k != i && k != j) f &= check(x[k], y[k], x[i], y[i], x[j], y[j]);
            if (f) {
                for (k = 0; k < 4; k++)
                    if (k != i && k != j) f &= fabs(dist(x[k], y[k], x[i], y[i]) - dist(x[k], y[k], x[j], y[j])) <= EPS;  // EPS防卡精度
                if (f) return "It's a square";
            }
        }
    return "Not a square";
}

T3 AnArray

题意

给定一个有N个元素数组A和一个整数K,让你求出符合一下条件的三元组个数:

(p, q, r),\space 0 \leq p < q< r < N, \space K | A_p\ast A_q\ast A_r

哦,还有,问题是Bob抛出来的。(还记得吗?那个博弈论里经常讲到的全世界最聪明的人)

分析

PART 0

看到三元组,马上回想起NOIP2015普及组T3,痛…
回来看题目,赤裸裸的数论味。

PART 1

首先,
O(N^3)的暴力很好打。
但是,看到2k的范围显然会TLE。

PART 2

看范围,应该是一个 O(N^2) 的复杂度。
我们可以枚举两个前两个数A_qA_p

  • 若这两个数的乘积已经被K整除,那么A_p后的所有数都满足条件。

  • 若不然,我们知道如果 K|A_p\ast A_q\ast A_r,那么乘积中一定含有K的全部素因子。考虑(A_p\ast A_q,K),这个gcd即这两数已经贡献的素因子乘积,那么我们只需要找到A_r含有所有K剩下的素因子就行了,即\frac {K} {(A_p\ast A_q,K)} | A_r(原谅表达能力有限)

然后前者可以直接求出,后者呢…可以把A排个序,那么若\frac {K} {(A_p\ast A_q,K)} | A_r,则A_r\geq \frac {K} {(A_p\ast A_q,K)},于是,来个二分,然后还是暴力地一个一个判过来。
嗯,这个复杂度比较信仰,但是当失去信仰,你会发现:
\text{还是} O(N^3)!!!
而且我打的时候还把这个打挂了:

using namespace std;
typedef long long LL;

class AnArray {
public:
    int solveProblem( vector <int> A, int K );
};

inline bool cmp(int a, int b) { return a > b; }
inline LL gcd(LL a, LL b) {
    if (a < b) swap(a, b);
    while (b) { a %= b; swap(a, b); }
    return a;
}
int AnArray::solveProblem(vector <int> A, int K) {
    int N = A.size();
    sort(A.begin(), A.end(), cmp);
    int i, j, ans = 0, limi, limj;
    for (i = 0, limi = N - 2, limj = N - 1; i < limi; i++)
        for (j = i + 1; j < limj; j++) {
            int m = gcd((long long)A[i] * A[j], K);
            int x = K / m;
            if (x == 1) ans += N - j - 1;
            else {
                vector<int>::iterator it = lower_bound(A.begin() + j + 1, A.end(), x, cmp);
                for (; it != A.end(); it++)
                    if (!(*it % x)) ans++;
            }
        }
    return ans;
}

于是我打的时候也就想到这么多,至于为什么没有后续呢,因为:

PART 3 去吃饭了!

先吃个饭。

PART 4

吃完饭继续分析。
其实我们在PART 2的时候,没有注意到一个非常主要的地方。
既然K|A_p\ast A_q\ast A_r,那么乘积中一定含有K的全部素因子,那么也就是说,只有KA_i都具有的素因子,才会产生一些贡献。再进一步,我们就只要管(K,A_i)产生的贡献就可以了。
那么就先把所有A_i都变成(K,A_i),这样一来,A_i就从原来10^9级别缩小到K10^6级别了。乍一看,这个缩小的规模没什么用,不过,后面你就会明白的。
哦,不对,你马上就会明白的。
我们仍然回到刚刚对后者情况的做法,我们不是要找A_r满足\frac {K} {(A_p*A_q,K)}|A_r吗?
我们刚刚把A_i的范围缩小到10^6,那么接下来只需要一个暴力统计一下被10^6中的数整除的A_i的个数就可以了。
也就是说,我们枚举三元组后面两个数,我们需要知道的就是它们前面\frac {K} {(A_q\ast A_r,K)}|A_p个数即可。
可以边做边统计,达到理论复杂度 O(N^2)

贺代码

using namespace std;
typedef long long LL;
const int MAXK = 1000005;

class {
public:
    int solveProblem( vector <int> A, int K );
};

template <typename T>
inline T gcd(T a, T b) {
    if (a < b) swap(a, b);
    while (b) { a %= b; swap(a, b); }
    return a;
}

int cnt[MAXK];

int AnArray::solveProblem(vector <int> A, int K) {
    int N = A.size(), i, ans = 0, limi, j;
    for (i = 0; i < N; i++) A[i] = gcd(A[i], K);
    memset(cnt, 0, sizeof(cnt));
    for (i = 1; (long long)i * i <= A[0]; i++)  // 先把A[0]的统计了,因为按我们的枚举方式,A[0]不可能作为三元组的第二个数或第三个数
        if (!(A[0] % i)) {  // A[0]的每个因子都要枚举,因为下面的x不一定恰好是A[i]
            cnt[i]++;
            if (i * i != A[0]) cnt[A[0] / i]++;
        }
    for (i = 1, limi = N - 1; i < limi; i++) {
        for (j = i + 1; j < N; j++) {
            int x = gcd((LL)A[i] * A[j], (LL)K);
            ans += cnt[K / x];
        }
        for (j = 1; (LL)j * j <= A[i]; j++) // 枚举完j再累加
            if (!(A[i] % j)) {
                cnt[j]++;
                if (j * j != A[i]) cnt[A[i] / j]++;
            }
    }
    return ans;
}

哼,想贺代码?

我能告诉你这个程序被卡常了吗?
其实我也不知道为什么,我把那个大数据拉到本地跑也是过的,不过给TC系统测一下就TLE了。
不过,很高兴地告诉你,只需要一堆优化开关,就可以卡过去。

总结

这场668相比667呢,667是以DP为主,而这场则主要是考验数学能力。因此呢,实现会比667畅快很多。

THE END

如果您有更好的做法,当然欢迎在评论区提出。
如果您认为我的题解有不足之处,请您务必指出。
如果您对我的题解有疑惑指出,也欢迎您提问。

About The Author

发表评论

电子邮件地址不会被公开。 必填项已用*标注