1. 程式人生 > 其它 >第十六屆東北地區大學生程式設計競賽M-Spiral

第十六屆東北地區大學生程式設計競賽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