第十六届东北地区大学生程序设计竞赛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; }