1. 程式人生 > 實用技巧 >2020牛客暑期多校訓練營(第二場)

2020牛客暑期多校訓練營(第二場)

目錄

Contest Info


傳送門

Solved A B C D E F G H I J K
11 / 11 Ø Ø O O Ø O Ø Ø Ø O Ø
  • O 在比賽中通過
  • Ø 賽後通過
  • ! 嘗試了但是失敗了
  • - 沒有嘗試

Solutions


A. All with Pairs

題意:
定義\(f(s,t)\)為最大的\(i\)滿足\(s\)串前\(i\)個與\(t\)串後\(i\)個相匹配。
現給出\(n\)個串,計算

\[\sum_{i=1}^n\sum_{j=1}^mf(s_i,s_j)^2 \]

\(\displaystyle n\leq 10^5,\sum |s_i|\leq 10^6\)

思路:
計算兩個串相匹配一般可以用hash,注意到所有後綴個數的和最多為\(10^6\)級別,我們首先儲存所有後綴的hash值。
對於每一個串的每一個字首,很容易計算出\(cnt[i]\)

即多少個字尾與其匹配。但是這樣並不能直接統計入答案,可能會出現重複計算,比如\(aba\),顯然\(a\)\(aba\)不能一起算,只能算後面的。
上例給了我們啟示,通過字串的next陣列去重即可,具體就是從小到大列舉\(i\)\(cnt[next[i]]-cnt[i]\)。這樣去重過後剩下的\(cnt[i]\)才是符合要求的。
我程式碼因為用map存了hash值,所以時間複雜度多了個log。實際上可以通過AC自動機+KMP做到線性複雜度,也即是通過fail樹計算出cnt陣列再通過next去重。
hash程式碼如下:

Code
// Author : heyuhhh
// Created Time : 2020/07/14 09:32:51
#include<bits/stdc++.h>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 1e6 + 5, MOD = 998244353;
void err(int x) {cerr << x;}
void err(long long x) {cerr << x;}
void err(double x) {cerr << x;}
void err(char x) {cerr << '"' << x << '"';}
void err(const string &x) {cerr << '"' << x << '"';}
void _print() {cerr << "]\n";}
template<typename T, typename V>
void err(const pair<T, V> &x) {cerr << '{'; err(x.first); cerr << ','; err(x.second); cerr << '}';}
template<typename T>
void err(const T &x) {int f = 0; cerr << '{'; for (auto &i: x) cerr << (f++ ? "," : ""), err(i); cerr << "}";}
template <typename T, typename... V>
void _print(T t, V... v) {err(t); if (sizeof...(v)) cerr << ", "; _print(v...);}
#ifdef Local
#define dbg(x...) cerr << "[" << #x << "] = ["; _print(x)
#else
#define dbg(x...)
#endif
typedef unsigned long long ull;
template <unsigned mod, unsigned base>
struct rolling_hash {
    unsigned int pg[N], val[N]; // val:1,2...n
    rolling_hash() {
        pg[0] = 1;
        for(int i = 1; i < N; i++) pg[i] = 1ull * pg[i - 1] * base % mod;
        val[0] = 0;
    }
    void build(const char *str) {
        for(int i = 0; str[i]; i++) {
            val[i + 1] = (str[i] + 1ull * val[i] * base) % mod;
        }
    }
    unsigned int operator() (int l, int r) {
        ++r; //
        return (val[r] - 1ull * val[l] * pg[r - l] % mod + mod) % mod;
    }
};
struct dm_hasher {
    //str:0,1...len-1
    rolling_hash<997137961, 753> h1;
    rolling_hash<1003911991, 467> h2;
    void build(const char *str) {
        h1.build(str); h2.build(str);
    }
    ull operator() (int l, int r) {
        return ull(h1(l, r)) << 32 | h2(l, r);
    }
}hasher;
 
int nxt[N];
 
void Get_next(const char *s) {
    int j, L = strlen(s);
    nxt[0] = j = -1;
    for(int i = 1; i < L; i++) {
        while(j >= 0 && s[i] != s[j + 1]) j = nxt[j] ;
        if(s[i] == s[j + 1]) j++;
        nxt[i] = j;
    }
}
 
int n;
string str[N];
unordered_map<ll, int> cnt;
 
void run() {
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> str[i];
        hasher.build(str[i].c_str());
        int len = str[i].length();
        for (int j = len - 1; j >= 0; j--) {
            cnt[hasher(j, len - 1)]++;
        }
    }
    int ans = 0;
    for (int i = 0; i < n; i++) {
        Get_next(str[i].c_str());
        hasher.build(str[i].c_str());
        int len = str[i].length();
        for (int j = 0; j < len; j++) {
            if (nxt[j] >= 0) {
                cnt[hasher(0, nxt[j])] -= cnt[hasher(0, j)];
            }
        }
        for (int j = 0; j < len; j++) {
            dbg(i, j, cnt[hasher(0, j)]);
            ans += 1ll * cnt[hasher(0, j)] % MOD * (j + 1) % MOD * (j + 1) % MOD;
            if (ans >= MOD) ans -= MOD;
        }
        for (int j = len - 1; j >= 0; j--) {
            if (nxt[j] >= 0) {
                cnt[hasher(0, nxt[j])] += cnt[hasher(0, j)];
            }
        }
    }
    cout << ans << '\n';
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cout << fixed << setprecision(20);
    run();
    return 0;
}

B. Boundary

列舉兩個點確定出圓心直接計數即可。
這裡通過sort來計數,比直接map來要快一些。

Code
// Author : heyuhhh
// Created Time : 2020/07/14 17:06:09
#include<bits/stdc++.h>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 2000 + 5;
const double eps = 1e-10;
typedef pair<double, double> pdd;
int n;
struct Point {
    int x, y;
} a[N];
pdd solve(Point a, Point b, Point c) //三點共圓圓心公式
{
    double fm1=2 * (a.y - c.y) * (a.x - b.x) - 2 * (a.y - b.y) * (a.x - c.x);
    double fm2=2 * (a.y - b.y) * (a.x - c.x) - 2 * (a.y - c.y) * (a.x - b.x);
    if (fm1 == 0 || fm2 == 0) {
        return MP(1e18, 1e18);
    }
    double fz1=a.x * a.x - b.x * b.x + a.y * a.y - b.y * b.y;
    double fz2=a.x * a.x - c.x * c.x + a.y * a.y - c.y * c.y;
 
    double X = (fz1 * (a.y - c.y) - fz2 * (a.y - b.y)) / fm1;
    double Y = (fz1 * (a.x - c.x) - fz2 * (a.x - b.x)) / fm2;
    return MP(X, Y);
}
 
bool operator == (pdd A, pdd B) {
    return fabs(A.fi - B.fi) <= eps && fabs(A.se - B.se) <= eps;
}
 
void run() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i].x >> a[i].y;
    }
    vector<pdd> res;
    for (int i = 1; i <= n; i++) {
        for (int j = i + 1; j <= n; j++) {
            pdd now = solve({0, 0}, a[i], a[j]);
            if (now == MP(1e18, 1e18)) continue;
            res.push_back(now);
        }
    }
    if (sz(res) == 0) {
        cout << 1 << '\n';
        return;
    }
    sort(all(res));
    int ans = 1, t = 1;
    pdd now = res[0];
    for (int i = 1; i < sz(res); i++) {
        if (res[i] == now) {
            ++t;
        } else {
            ans = max(ans, t);
            now = res[i];
            t = 1;
        }
    }
    ans = max(ans, t);
    for (int i = 2; i <= n; i++) {
        if (i * (i - 1) == 2 * ans) {
            cout << i << '\n';
            return;
        }
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cout << fixed << setprecision(20);
    run();
    return 0;
}

C. Cover the Tree

設葉子結點個數為\(x\),那麼答案下界即為\(\lceil\frac{x}{2}\rceil\)
實際上可以通過構造達到該下界,首先我們找到一個度數大於\(1\)的結點作為根,然後要使葉子結點儘量不在某顆子樹內匹配完,否則會多出一條邊。所以感受一下就會發現把葉子結點劃為兩部分,之後對應相匹配就行。

Code
// Author : heyuhhh
// Created Time : 2020/07/13 12:22:14
#include<bits/stdc++.h>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 2e5 + 5;

vector<int> G[N];
int n;
vector<int> v;
void dfs(int u, int fa) {
    int sons = 0;
    for (auto v : G[u]) {
        if (v != fa) {
            dfs(v, u);
            ++sons;
        }
    }
    if (sons == 0) {
        v.push_back(u);
    }
}

int d[N];

void run() {
    cin >> n;
    for (int i = 0; i < n - 1; i++) {
        int u, v; 
        cin >> u >> v;
        G[u].push_back(v);
        G[v].push_back(u);
        ++d[u], ++d[v];
    }
    int rt = 1;
    for (int i = 1; i <= n; i++) {
        if (d[i] > 1) {
            rt = i;
        }
    }
    dfs(rt, 0);
    int len = sz(v);
    int t = (len + 1) / 2;
    vector<pii> ans;
    for (int i = 0; i + t < len; i++) {
        ans.push_back(MP(v[i], v[i + t]));
    }
    if (len & 1) {
        if (v[len / 2] != rt) {
            ans.push_back(MP(v[len / 2], rt));
        }
    }
    cout << sz(ans) << '\n';
    for (auto it : ans) {
        cout << it.fi << ' ' << it.se << '\n';
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cout << fixed << setprecision(20);
    run();
    return 0;
}

D. Duration

簽到。

Code
import datetime
def foo():
    a = 200
    print(a)
     
def minNums(startTime, endTime):
     
     
    startTime1 = startTime
    endTime1 = endTime
     
    startTime2 = datetime.datetime.strptime(startTime1, "%Y-%m-%d %H:%M:%S")
    endTime2 = datetime.datetime.strptime(endTime1, "%Y-%m-%d %H:%M:%S")
    seconds = (endTime2 - startTime2).seconds
     
    total_seconds = (endTime2 - startTime2).total_seconds()
    if total_seconds < 0:
        total_seconds = -total_seconds
    print(int(total_seconds))
    mins = total_seconds / 60
    return int(mins)
 
if __name__ == "__main__":
    s='2020-07-28 '
    st=input()
    startTime_1 = s+st
    en=input()
    endTime_1 = s+en
    fenNum = minNums(startTime_1, endTime_1)

E. Exclusive OR

題意:
給出\(n\)個數\(a_1,a_2,\cdots,a_n\),接下來要輸出\(n\)個數,第\(i\)個數為從序列中選出\(i\)個可重複的數,異或的最大值。
\(1\leq n\leq 2\cdot 10^5,0\leq a_i<2^{18}\)

思路:
首先容易發現線性空間的秩為\(18\),也就是最多\(n=18\)時能取到異或的最大值。
並且也很容易發現\(ans_i\geq ans_{i-2}\)
所以小範圍考慮暴力,大範圍直接通過取等號即可,暴力的話直接通過FWT對值域卷積即可。
正常來想當\(i\geq 19\)時不等式取等號,即\(ans_i=ans_{i-2}\)
但實際上這裡要取到\(20\)。也就是\(ans_{19}>ans_{17}\)是可能會存在的,並且也可以構造相應的資料出來。這裡可以這樣想,我們將第\(18\)個數和第\(19\)個數兩個數的異或值看作一個新的第\(18\)個數來異或出最大值,這肯定沒問題;對於\(i\geq 20\)時,新的第\(18\)個數是至少三個數異或起來的,這肯定沒必要,因為我們只拿一個數,另外偶數個數相互抵消就行,也就是此時只能取等號。
所以異或卷積\(19\)次把小範圍情況解決啦就行。

Code
// Author : heyuhhh
// Created Time : 2020/07/14 14:38:56
#include<bits/stdc++.h>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 1e6 + 5;

void FWT_xor(int *a, int n, int op) {
    for(int i = 1; i < n; i <<= 1)
        for(int p = i << 1, j = 0; j < n; j += p)
            for(int k = 0; k < i; k++) {
                int X = a[j + k], Y = a[i + j + k];
                a[j + k] = (X + Y); a[i + j + k] = (X - Y);
                if(op == -1) a[j + k] = a[j + k] / 2, a[i + j + k] = a[i + j + k] / 2;
            }
}

int n;
int a[N], b[N];
int ans[N];

void run() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        int x; cin >> x;
        a[x] = b[x] = 1;
        ans[1] = max(ans[1], x);
    }

    int L = (1 << 18);  
    FWT_xor(b, L, 1);  
    for (int k = 2; k <= 19; k++) {
        FWT_xor(a, L, 1);
        for (int i = 0; i < L; i++) {
            a[i] *= b[i];
        }
        FWT_xor(a, L, -1);
        for (int i = 0; i < L; i++) {
            if (a[i] > 0) {
                ans[k] = i;
                a[i] = 1;
            }
        }
    }
    for (int i = 1; i <= n; i++) {
        if (i > 19) ans[i] = ans[i - 2];
        cout << ans[i] << " \n"[i == n];
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cout << fixed << setprecision(20);
    run();
    return 0;
}

F. Fake Maxpooling

二維單調佇列模板題。

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5005, M = 5000, T = 1000;
int a, b, n;
int mp[N][N], tmp[N][2];
int q1[N][N], l1[N], r1[N];
int Q1[N];

int Gcd[N][N];

int main() {
    ios::sync_with_stdio(false); cin.tie(0) ;
    cin >> a >> b >> n;
    for (int i = 1; i <= a; i++) {
        for (int j = 1; j <= b; j++) {
            if (!Gcd[i][j]) {
                for (int k = 1; k * i <= a && k * j <= b; k++)
                    Gcd[k * i][k * j] = k, mp[k * i][k * j] = i * j * k;
            }
        }
    }

    for(int i = 1; i <= b; i++) l1[i] = 1;
    ll ans = 0;
    for(register int i = 1; i <= a; i++) {
        for(register int j = 1; j <= b; j++) {
            while(l1[j] <= r1[j] && mp[q1[j][r1[j]]][j] <= mp[i][j]) r1[j]--;
            q1[j][++r1[j]] = i ;
            while(l1[j] <= r1[j] && i + 1 - q1[j][l1[j]] > n) l1[j]++;
        }
        for(register int j = 1; j <= b; j++) tmp[j][0] = mp[q1[j][l1[j]]][j];
        int cc1, cc2;
        int L1 = 1, R1 = 0, L2 = 1, R2 = 0;
        for(register int j = 1; j <= b; j++) {
            while(L1 <= R1 && tmp[Q1[R1]][0] <= tmp[j][0]) R1--;
            Q1[++R1] = j ;
            while(L1 <= R1 && j + 1 - Q1[L1] > n) L1++;
            if(i >= n && j >= n) {
                ans += tmp[Q1[L1]][0];
            }
        }
    }
    cout << ans;
    return 0;
}

G. Greater and Greater

維護的是對於每個\(b_i\),哪些位置\(a_j>=b_i\),然後從\(b_1\)開始確定出哪些\(a\)能作為區間的第一個,自然會產生區間第二個位置的備選集合,我們只需要拿\(b_2\)的bitset與備選集合交一下就能確定哪些\(a\)能作為第二個...依次往下操作就行了。
細節見程式碼:

Code
// Author : heyuhhh
// Created Time : 2020/07/14 11:41:23
#include<bits/stdc++.h>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 150000 + 5, M = 40000 + 5;

int n, m;
int a[N], b[M];

int p1[N], p2[N];

bitset<N> res, cur;

void run() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    for (int i = 1; i <= m; i++) {
        cin >> b[i];
    }
    iota(p1 + 1, p1 + n + 1, 1);
    iota(p2 + 1, p2 + m + 1, 1);
    sort(p1 + 1, p1 + n + 1, [&](int i, int j) {
        return a[i] > a[j];
    });
    sort(p2 + 1, p2 + m + 1, [&](int i, int j) {
        return b[i] > b[j];
    });
    res.set();
    for (int i = 1, p = 1; i <= m; i++) {
        while (p <= n && a[p1[p]] >= b[p2[i]]) {
            cur.set(p1[p]);
            ++p;
        }
        res &= (cur >> (p2[i] - 1));
    }
    int ans = res.count();
    cout << ans << '\n';
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cout << fixed << setprecision(20);
    run();
    return 0;
}

H. Happy Triangle

看到了一個很巧妙的寫法,寫起來也不容易錯。
因為判斷是否能構成三角形主要是要check這個條件:\(a+b>c,a<c,b<c\)
假設我們已知\(a,b\),未知\(x\),直接想的話肯定是按照上述條件來進行check,這樣也可以做的,但是比較麻煩。
實際上既然我們已知\(a,b,a<b\),那麼此時自然會產生一個答案合法區間\((b-a,a+b)\),如果有\(x\)落在這個區間範圍內說明合法。
那麼只需要通過線段樹維護區間加減以及單點查詢即可。
要離散化一下才行,不過離散化寫起來程式碼麻煩了許多,其實可以直接標記永久化+動態開點,這樣寫起來也很容易!標記永久化感覺還是挺好用的,尤其是對於區間上面的操作。
細節見程式碼吧:

Code
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
const int rb = 1e9;

int Add[N * 21], ls[N * 21], rs[N * 21];
int T, rt;
void update(int& o, int l, int r, int L, int R, int v) {
    if (!o) o = ++T;
    if (L <= l && r <= R) {
        Add[o] += v;
        return;
    }
    int mid = (l + r) >> 1;
    if (L <= mid) update(ls[o], l, mid, L, R, v);
    if (R > mid) update(rs[o], mid + 1, r, L, R, v);
}
void update(int l, int r, int v) {
    ++l, --r;
    l = max(l, 1), r = min(r, rb);
    if (l > r) return;
    update(rt, 1, rb, l, r, v);
}
int query(int o, int l, int r, int p) {
    if (!o) return 0;
    if (l == r) return Add[o];
    int mid = (l + r) >> 1;
    if (p <= mid) return query(ls[o], l, mid, p) + Add[o];
    else return query(rs[o], mid + 1, r, p) + Add[o];
}

int main() {
    multiset<int> s;
    int op, x, q;
    auto gao = [&](multiset<int>::iterator l, multiset<int>::iterator r, int v) {
        if (l != s.begin()) {
            --l;
            update(x - *l, x + *l, v);
            if (r != s.end()) update(*r - *l, *r + *l, -v);
        }
        if (r != s.end()) update(*r - x, *r + x, v);
        return 0;
    };
    scanf("%d", &q);
    while (q--) {
        scanf("%d%d", &op, &x);
        if (op == 1) {
            auto r = s.upper_bound(x), l = r;
            gao(l, r, 1);
            s.insert(x);
        } else if (op == 2) {
            auto r = s.upper_bound(x), l = r; --l;
            gao(l, r, -1);
            s.erase(--r);
        } else {
            int t = query(rt, 1, rb, x);
            if (t > 0) cout << "Yes" << '\n';
            else cout << "No" << '\n';
        }
    }
    return 0;
}

I. Interval

直接將該二維問題轉化為在網格圖上的問題,那麼會發現是一個很簡單的最小割,類似於“狼抓兔子”。
這類問題一般是將網格圖轉化為對偶圖,然後跑個最短路就行了。因為直接衝網路流的話很可能會TLE,因為圖的規模太大了,如果找到其對偶圖的話圖的規模沒怎麼變,但是最短路能把時間複雜度降下來。
跟“狼抓兔子”很像,難度差不多。

Code
// Author : heyuhhh
// Created Time : 2020/07/14 20:52:56
#include<bits/stdc++.h>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 500 + 5, M = 252000 + 5;

int n, m;
int grid[N][N];
int T, s, t;

struct Edge{
    int v, w, next;   
}e[M << 1];
ll dis[M];
struct Dijkstra{
    struct node{
        ll d, u;
        bool operator < (const node &A) const {
            return d > A.d;
        }   
    };
    int head[M], tot;
    bool vis[M];
    void init() {
        memset(head, -1, sizeof(head)); tot = 0;   
    }
    void adde(int u, int v, int w) {
        e[tot].v = v; e[tot].w = w; e[tot].next = head[u]; head[u] = tot++;   
    }
    void dij(int s) {
        priority_queue <node> q;
        memset(dis, INF, sizeof(dis));
        memset(vis, 0, sizeof(vis));
        dis[s] = 0;
        q.push(node{0, s});
        while(!q.empty()) {
            node cur = q.top(); q.pop();
            int u = cur.u, d = cur.d;
            if(vis[u]) continue;
            vis[u] = 1;
            for(int i = head[u]; i != -1; i = e[i].next) {
                int v = e[i].v;
                if(dis[v] > dis[u] + e[i].w) {
                    dis[v] = dis[u] + e[i].w;
                    q.push(node{dis[v], v});   
                }
            }   
        }
    }
}solver;

void run() {
    solver.init();
    cin >> n >> m;
    s = ++T, t = ++T;
    for (int i = 1; i <= n; i++) {
        for (int j = i + 1; j <= n; j++) {
            grid[i][j] = ++T;
        }
    }
    for (int i = 2; i <= n; i++) {
        grid[0][i] = s;
    }
    for (int i = 1; i < n; i++) {
        grid[i][n + 1] = t;
    }
    while (m--) {
        int l, r, c;
        string dir;
        cin >> l >> r >> dir >> c;
        if (dir == "L") {
            solver.adde(grid[l][r], grid[l][r + 1], c);
            solver.adde(grid[l][r + 1], grid[l][r], c);
        } else {
            solver.adde(grid[l - 1][r], grid[l][r], c);
            solver.adde(grid[l][r], grid[l - 1][r], c);
        }
    }
    solver.dij(s);
    ll ans = dis[t];
    if (ans >= 1000000000000) {
        ans = -1;
    }
    cout << ans << '\n';
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cout << fixed << setprecision(20);
    run();
    return 0;
}

J. Just Shuffle

置換類的問題一般考慮為一張有向圖上面的問題。
這個題中建出來的圖是走了\(k\)步過後每個點應該去往哪個點,要找到只走一步每個點該走到哪個點,只需要對每一個環走\(inv_{k\% len}\)步即可,因為\(k\cdot inv_{k}\equiv 1(mod\ len)\),因為\(k\)是一個大質數,所以這是肯定有解的。程式碼中通過擴充套件歐幾里得演算法求解\(inv_k\)。(不能通過快速冪來求逆元,因為不滿足費馬小定理的條件,即模數不為質數)。

Code
// Author : heyuhhh
// Created Time : 2020/07/13 14:01:34
#include<bits/stdc++.h>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 1e6 + 5, MOD = 998244353;
 
int n, k;
vector<int> G[N];
bool vis[N];
int p[N];
 
vector<int> v;
 
void dfs(int u) {
    v.push_back(u);
    vis[u] = true;
    for (auto son : G[u])
        if (!vis[son]) {
            dfs(son);
        }
}
 
void exgcd(ll a, ll b, ll &x, ll &y) {
    if(b == 0) {
        x = 1, y = 0;
        return ;
    }
    exgcd(b,a%b,x,y);
    ll z = x ;
    x = y;
    y = z - y * (a / b);
}
ll calc(ll a, ll b, ll c) {
    ll x, y;
    ll g = __gcd(a, b);
    if(c % g != 0) return -1;
    a /= g, b /= g, c /= g;
    exgcd(a, b, x, y);
    x *= c;
    x = (x % b + b) % b;
    return x;
}
 
void gao(vector<int>& v) {
    int len = sz(v);
    int r = k % len;
    int c = calc(r, len, 1);
    // c: steps need to go
    if (c == -1) {
        cout << -1 << '\n';
        exit(0);
    }
    vector<int> res(len);
    for (int i = 0; i < len; i++) {
        res[i] = v[(i + c) % len];
    }
    swap(res, v);
}
 
int in[N], out[N];
 
void run() {
    cin >> n >> k;
    for (int i = 1; i <= n; i++) {
        cin >> p[i];
        ++out[i], ++in[p[i]];
        G[i].push_back(p[i]);
    }
    for (int i = 1; i <= n; i++) {
        if (in[i] != 1 || out[i] != 1) {
            cout << -1 << '\n';
            return;
        }
    }
    for (int i = 1; i <= n; i++) {
        if (!vis[i]) {
            v.clear();
            dfs(i);
            vector<int> w = v;
            gao(v);
            for (int j = 0; j < sz(w); j++) {
                p[w[j]] = v[j];
            }
        }
    }
    for (int i = 1; i <= n; i++) {
        cout << p[i] << " \n"[i == n];
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cout << fixed << setprecision(20);
    run();
    return 0;
}

K. Keyboard Free

比較簡單的方法,直接模擬積分即可,固定\(A\)為定點(\(A\)轉動起來沒啥用,因為求的是平均意義下的,每個\(A\)所在位置不同答案都是一樣的),然後直接列舉\(B,C\)的位置,然後通過叉積計算面積就行。
當然還有比較硬核的數學計算方法,通過積分求出來三角形的期望高度,然後計算,積分是通過手算的,我感覺還是有點複雜。。

Code
// Author : heyuhhh
// Created Time : 2020/07/15 11:11:44
#include<bits/stdc++.h>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 1e5 + 5;
const double pi = acos(-1.0);
vector<double> Sin(500), Cos(500);
void init() {
    for (int i = 0; i < 500; i++) {
        double a = 2.0 * pi * i / 500;
        Sin[i] = sin(a);
        Cos[i] = cos(a);
    }
}
void run() {
    vector<int> r(3);
    for (int i = 0; i < 3; i++) {
        cin >> r[i];
    }
    sort(all(r));
    double ans = 0;
    for (int i = 0; i < 500; i++) {
        double a = 2.0 * pi * i / 500;
        double x2 = r[1] * Cos[i], y2 = r[1] * Sin[i];
        for (int j = 0; j < 500; j++) {
            double b = 2.0 * pi * j / 500;
            double x3 = r[2] * Cos[j], y3 = r[2] * Sin[j];
            ans += fabs((x2 - r[0]) * y3 - (x3 - r[0]) * y2);
        }
    }
    ans = ans / 500 / 500 / 2;
    cout << ans << '\n';
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cout << fixed << setprecision(1);
    init();
    int T; cin >> T; while(T--)
    run();
    return 0;
}