第十六届东北地区大学生程序设计竞赛M-Spiral

题意,你从原点出发,想走出一个k边形,一开始你向(1,0)方向走,走长度为1的直线。

 每走一步,你都会转弯,转的角度是$\frac{2\pi}{k}$,也就是每条边的角度都是$k$边形的一条边。

然后每走$k/2$下取整步,每步走的长度就会+1。问走了$n$米之后,点的坐标是多少。

 

赛场被喂了假思路往错误的方向想了好久,但是事实上题解做法十分简单。

在赛上,队友给的思路是,有些点在一条直线上,然后找规律。事实上真有规律。

偶数的时候模$k$相同的点在同一条直线上,且每次往外走相同的长度。

奇数的时候模$k*(k/2)$相同的点在同一条直线上,且每次往外走相同的长度。

 

然后发现不好做,因为奇数的时候,就算第一圈的长度也有$k^2$级别的,没法很快算出每个点坐标。

 

之后看了题解,发现利用复数,可以飞快算出每个点坐标。

 

复数的作用类似旋转矩阵,可以用k次单位根$w_k^{(1)}$乘上一个复数表示旋转。且复数的性质跟实数基本相同。

我们用复数表示向量,第i个顶点的位置就是$e+ew+ew^2+...+2ew^d+...+kew^{i-1},e=1+i$

借用题解中的图更加直观。

假设一共有$(s + 1)$行,最后一行有$(t + 1)$个元素

把这个矩阵分成三部分:除去第$(s + 1)$行的剩下矩阵,第$(s + 1)$行的前面$t$个,第$(s + 1)$行的最后一个。

满足$n=(1 + s) * s / 2 * d + t * (s + 1) + x$

$s, t, x$都十分好算,用求根公式可以算出s,再搞搞可以搞出$t$跟$x$。

 

然后主要问题就是算整个式子的和。

先求第一部分的和:

$$T_1 = \sum_{i=1}^{d}\omega^{i-1}\sum_{j=1}^{s}j\omega^{d(j-1)}$$

前后两部分是独立的,第一个求和号用等比数列求和式,第二个求和号用高中的做差方法:

$$S = \sum_{j=1}^{s}j\omega^{d(j-1)}$$
$$\omega S = \sum_{j=2}^{s}j\omega^{d(j-1)}$$

相差之后,前面用等比求和,再减去最后一项,得到

$$S = \frac{s\omega^{ds}(\omega^d-1)-\omega^{ds}+1}{(\omega^d-1)^2}$$

$$T_1 = \frac{\omega^d-1}{\omega-1}\frac{s\omega^{ds}(\omega^d-1)-\omega^{ds}+1}{(\omega^d-1)^2}$$

 

第二部分:

$$T_2 = (s + 1)\omega^{sd}\sum_{i=1}^t\omega^{i-1}$$

 

是更简单的等比数列

 

第三部分只有一项:

$$T_3 = x\omega^{sd+t}$$

 

每一项都可以用快速幂求出来,然后加起来就行了。

 

时间复杂度$O(Tlogn)$

//
// Created by onglu on 2022/5/26.
//

#include <bits/stdc++.h>
#define double long double
#define cpab const Point &a, const Point &b
#define all(a) a.begin(),a.end()
#define rall(a) a.rbegin(),a.rend()

#define endl '\n'
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define Mid ((l + r) / 2)
#define int long long
using namespace std;
const int N = 2e6 + 1009;
//const int N = 2e5 + 1009;
//const int N = 5009;
//const int N = 309;
const double eps = 1e-9;
const double Pi = acos(-1.0);
struct Point {
    double x, y;
    Point() {}
    Point(double x, double y) : x(x), y(y) {}
};
istream &operator>>(istream& in, Point &p) {
    in >> p.x >> p.y;
    return in;
}
ostream &operator<<(ostream& out, Point &p) {
    out << "(" << p.x << ", " << p.y << ")";
    return out;
}
Point operator+(cpab) { return {a.x + b.x, a.y + b.y}; }
Point operator-(cpab) { return {a.x - b.x, a.y - b.y}; }
Point operator+(const Point &a, const double &b) { return {a.x + b, a.y}; }
Point operator-(const Point &a, const double &b) { return {a.x - b, a.y}; }
Point operator*(const Point &a, const double &b) { return {a.x * b, a.y * b}; }
Point operator*(const double &b, const Point &a) { return {a.x * b, a.y * b}; }
Point operator/(const Point &a, const double &b) { return {a.x / b, a.y / b}; }
Point operator*(cpab) { return {a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x}; }
Point operator/(cpab) { return a * Point(b.x, -b.y) / (b.x * b.x + b.y * b.y); }


int n = 5, k = 7, a[N];
Point Pow(Point a, int p) {
    Point ans(1, 0);
    for( ; p; p >>= 1, a = a * a)
        if(p & 1)
            ans = ans * a;
    return ans;
}
void work() {
    cin >> n >> k;
    int d = k / 2;
    int s = (1 + sqrt(1 + 8 * n / d)) / 2;
    if(s * (s + 1) / 2 * d > n) s -= 1;
    if(s * (s + 1) / 2 * d < n && (s + 2) * (s + 1) / 2 * d <= n) s -= 1;
    int t = (n - s * (s + 1) / 2 * d) / (s + 1);
    int x = (n - s * (s + 1) / 2 * d) % (s + 1);
    Point w(cos(2 * Pi / k), sin(2 * Pi / k));
    Point wsd = Pow(w, s * d);
    Point wt = Pow(w, t), wsdt = wsd * wt;
    Point ans = (s * wsd * (Pow(w, d) - 1) - wsd + 1) / (w - 1) / (Pow(w, d) - 1);
    ans = ans + (s + 1) * wsd / (w - 1) * (wt - 1);
    ans = ans + x * wsdt;
    cout << (ans.x + ans.y) << endl;
}

signed main() {
#ifdef LOCAL
    freopen("C:\\Users\\onglu\\CLionProjects\\acm\\data.in", "r", stdin);
    freopen("C:\\Users\\onglu\\CLionProjects\\acm\\data.out", "w", stdout);
#endif
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout << fixed << setprecision(10);
    int Case = 1;
   cin >> Case;
    while(Case--) work();
    return 0;
}
View Code

 

posted @ 2022-05-27 18:57  _onglu  阅读(132)  评论(0编辑  收藏  举报