1. 程式人生 > 實用技巧 >2020 Multi-University Training Contest 6

2020 Multi-University Training Contest 6

目錄

Contest Info


傳送門

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

Solutions


A. Road To The 3rd Building

考慮每個長度對答案的貢獻,並且將和式轉化為字首和的形式。那麼容易發現對於每個長度\(l\),只有前面\(0...l-1\)是負貢獻,後面\(l+1...n\)是正貢獻。所以再統計字首的字首就可求出答案。
注意\(l\)的答案是具有對稱性的。稍微手模一下找下規律即可。

Code
// Author : heyuhhh
// Created Time : 2020/08/06 13:05:06
#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, MOD = 1e9 + 7;
int qpow(ll a, ll b) {
    ll res = 1;
    while(b) {
        if (b & 1) res = res * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return res;
}
int n;
int a[N], ssum[N], sum[N];

void run() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        ssum[i] = (ssum[i - 1] + a[i]) % MOD;
        sum[i] = (sum[i - 1] + ssum[i]) % MOD;
    }
    int ans = 0;
    int p = (n + 1) / 2;
    for (int l = 1; l <= n; l++) {
        int res = 0;
        if (l <= p) {
            res = ((ll)sum[n] - sum[n - l] - sum[l - 1] + MOD + MOD) % MOD;
        } else {
            int qq = n - l + 1;
            res = ((ll)sum[n] - sum[n - qq] - sum[qq - 1] + MOD + MOD) % MOD;
        }

        res = 1ll * res * qpow(l, MOD - 2) % MOD;
        ans = (ans + res) % MOD;
    }
    ans = ans * 2 % MOD;
    int fm = qpow(1ll * n * (n + 1) % MOD, MOD - 2);
    ans = 1ll * ans * fm % MOD;
    cout << ans << '\n';
}
int main() {
#ifdef Local
    freopen("input.in", "r", stdin);
#endif
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cout << fixed << setprecision(20);
    int T; cin >> T; while(T--)
    run();
    return 0;
}

B. Little Rabbit's Equation

貌似模擬一下就行。

C. Borrow

題意:
給出三個數,每次最大的那個數會減一,然後另外兩個數隨機選一個加一。
求三個數都相等的期望操作次數。

思路:
首先令\(k=\frac{x+y+z}{3}\)
一開始思路是\(f_x\)表示最大值從\(x\rightarrow k\)的期望次數,但是由於有些情況比較複雜,頂部的值變化情況較多。但是最小的值始終處於增大狀態,所以我們定義\(f_{x}\)表示最小值\(x\rightarrow k\)就行了。
然後就有\(\displaystyle f_x=1+\frac{1}{2}f_x+\frac{1}{2}f_{x+1}\)

,由於\(f_k=0\),易知\(f_x=2\cdot(k-x)\)
上述情況是在\(x<k\leq y\leq z\)的情況下,\(x\leq y<k\leq z\)的情況不好處理。這時我們其實可以直接列舉所有可能情況就行,比如我列舉\(i\)次使得\(x\)先到達\(k\)或者讓\(y\)先到達,那麼問題就轉化為了第一種情況。由於我是列舉所有可能情況,所以概率也很好算。
細節見程式碼:

Code
// Author : heyuhhh
// Created Time : 2020/08/07 20:33:27
#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 qpow(ll a, ll b) {
    ll res = 1;
    while(b) {
        if(b & 1) res = res * a % MOD;
        a = a * a % MOD;
        b >>= 1;   
    }
    return res;   
}
int fac[N], inv[N];
void init() {
    fac[0] = 1;
    for(int i = 1; i < N; i++) fac[i] = 1ll * fac[i - 1] * i % MOD;
    inv[N - 1] = qpow(fac[N - 1], MOD - 2);
    for(int i = N - 2; i >= 0; i--) inv[i] = 1ll * inv[i + 1] * (i + 1) % MOD;
}
int C(int n, int m) {
    if (n < m) return 0;
    return 1ll * fac[n] * inv[m] % MOD * inv[n - m] % MOD;
}
void run() {
    vector<int> a(3);
    int sum = 0;
    int k;
    for (int i = 0; i < 3; i++) {
        cin >> a[i];
        sum += a[i];
    }
    if (sum % 3 != 0) {
        cout << -1 << '\n';
        return;
    }
    
    sort(all(a));
    k = sum / 3;
    if (a[1] >= k) {
        cout << 2 * (k - a[0]) << '\n';
        return;
    }
    int d1 = k - a[0], d2 = k - a[1];
    int ans = 0;
    int inv2 = qpow(2, MOD - 2);
    int tt = inv2;
    for (int i = 1, res; i < d1 + d2; i++) {
        res = 1ll * tt * C(i - 1, d1 - 1) % MOD * (i + 2 * (d1 + d2 - i)) % MOD;
        ans = (ans + res) % MOD;
        res = 1ll * tt * C(i - 1, d2 - 1) % MOD * (i + 2 * (d1 + d2 - i)) % MOD;
        ans = (ans + res) % MOD;
        tt = 1ll * tt * inv2 % MOD;
    }
    cout << ans << '\n';
}
int main() {
#ifdef Local
    freopen("input.in", "r", stdin);
#endif
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cout << fixed << setprecision(20);
    init();
    int T; cin >> T; while(T--)
    run();
    return 0;
}

E. Fragrant numbers

猜測僅有前面幾個數即可組合出所有的情況,並且最後的數的集合不大,所以直接暴力區間dp轉移,打個表然後交就行。

F. A Very Easy Graph Problem

最後答案是一顆生成樹,那麼就將問題轉化為了在生成樹上統計黑白點對的問題。

Code
// Author : heyuhhh
// Created Time : 2020/08/06 12:24:17
#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, MOD = 1e9 + 7;

int pow2[N];

void init() {
    pow2[0] = 1;
    for (int i = 1; i < N; i++) {
        pow2[i] = 1ll * pow2[i - 1] * 2 % MOD;
    }
}

int n, m;
vector<pii> G[N];
int f[N];
int find(int x) {
    return f[x] == x ? f[x] : f[x] = find(f[x]);
}

bool merge(int x, int y) {
    int fx = find(x), fy = find(y);
    if (fx != fy) {
        f[fx] = fy;
        return true;
    }
    return false;
}

void add(int& x, int y) {
    x += y;
    if (x >= MOD) x -= MOD;
}

void del(int& x, int y) {
    x -= y;
    if (x < 0) x += MOD;
}

int sz[N];
int a[N], tot;
int sum;

void dfs(int u, int fa) {
    sz[u] = 1 - a[u];
    for (auto it : G[u]) {
        int v = it.fi, w = it.se;
        if (v != fa) {
            dfs(v, u);
            add(sum, 1ll * w * sz[v] % MOD);
            sz[u] += sz[v];
        }
    }
}

int ans;

void dfs2(int u, int fa) {
    if (a[u] == 1) {
        add(ans, sum);
    }
    for (auto it : G[u]) {
        int v = it.fi, w = it.se;
        if (v != fa) {
            add(sum, 1ll * w * (tot - sz[v]) % MOD);
            del(sum, 1ll * w * sz[v] % MOD);

            dfs2(v, u);

            del(sum, 1ll * w * (tot - sz[v]) % MOD);
            add(sum, 1ll * w * sz[v] % MOD);
        }
    }
}

void run() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        f[i] = i;
        G[i].clear();
    }
    ans = sum = tot = 0;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        tot += 1 - a[i];
    }
    for (int i = 1; i <= m; i++) {
        int u, v;
        cin >> u >> v;
        if (merge(u, v)) {
            G[u].push_back(MP(v, pow2[i]));
            G[v].push_back(MP(u, pow2[i]));
        }
    }
    dfs(1, 0);
    dfs2(1, 0);
    cout << ans << '\n';
}
int main() {
#ifdef Local
    freopen("input.in", "r", stdin);
#endif
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cout << fixed << setprecision(20);
    init();
    int T; cin >> T; while(T--)
    run();
    return 0;
}

G. A Very Easy Math Problem

題意:
給定\(n,x,k\),求:

\[\sum_{a_1=1}^n\sum_{a_2=1}^n\cdots\sum_{a_x=1}^n(\prod_{j=1}^xa_j^k)f(gcd(...))gcd(...) \]

其中\(f(t)\)表示是否存在一個大於\(1\)的數\(p\),使得\(p^2|t\)\(gcd\)我就直接簡寫了。

思路:
這種題就是推推推,都是套路。

\[\begin{aligned} &\sum_{a_1=1}^n\sum_{a_2=1}^n\cdots\sum_{a_x=1}^n(\prod_{j=1}^xa_j^k)f(gcd(...))gcd(...)\\ =&\sum_{d=1}^n\sum_{a_1=1}^n\sum_{a_2=1}^n\cdots\sum_{a_x=1}^n(\prod_{j=1}^xa_j^k)f(d)d[gcd(...)==d]\\ =&\sum_{d=1}^nd^{kx}\sum_{a_1=1}^{n/d}\sum_{a_2=1}^{n/d}\cdots\sum_{a_x=1}^{n/d}(\prod_{j=1}^xa_j^k)f(d)d[gcd(...)==1]\\ =&\sum_{d=1}^nd^{kx}\sum_{a_1=1}^{n/d}\sum_{a_2=1}^{n/d}\cdots\sum_{a_x=1}^{n/d}(\prod_{j=1}^xa_j^k)f(d)d\sum_{t|...}\mu(t)\\ =&\sum_{t=1}^n\mu(t)t^{kx}\sum_{d=1}^nd^{kx}\sum_{a_1=1}^{n/td}\sum_{a_2=1}^{n/td}\cdots\sum_{a_x=1}^{n/td}(\prod_{j=1}^xa_j^k)f(d)d\\ =&\sum_{T=1}^nT^{kx}\sum_{t|T}\mu(t)F(T/t)\frac{T}{t}S(n/T)\\ =&\sum_{T=1}^nT^{kx}S(n/T)\sum_{t|T}\mu(t)F(T/t)\frac{T}{t} \end{aligned} \]

然後後面一部分通過類似於調和級數的東西預處理出來,\(S\)也可以直接預處理。
那麼數論分塊一下就沒了。

Code
// Author : heyuhhh
// Created Time : 2020/08/06 14:43:02
#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, MOD = 1e9 + 7;

int qpow(ll a, ll b) {
    ll res = 1;
    while(b) {
        if (b & 1) res = res * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return res;
}

int mu[N], p[N], phi[N];
bool chk[N];
void init() {
    mu[1] = phi[1] = 1;
    int cnt = 0, k = N - 1;
    for(int i = 2; i <= k; i++) {
        if(!chk[i]) p[++cnt] = i, mu[i] = -1, phi[i] = i - 1;
        for(int j = 1; j <= cnt && i * p[j] <= k; j++) {
            chk[i * p[j]] = 1;
            if(i % p[j] == 0) {mu[i * p[j]] = 0; phi[i * p[j]] = phi[i] * p[j]; break;}
            mu[i * p[j]] = -mu[i]; phi[i * p[j]] = phi[i] * (p[j] - 1);
        }
    }
}

bool f[N];
int F[N], G[N];
int S[N], val[N];

void run() {
    int T, k, x;
    cin >> T >> k >> x;
    for (int i = 1; i < N; i++) {
        f[i] = 1;
    }
    for (int t = 2; 1ll * t * t < N; t++) {
        for (int i = 1; 1ll * t * t * i < N; i++) {
            f[i * t * t] = 0;
        }
    }
    for (int i = 1; i < N; i++) {
        for (int j = i; j < N; j += i) {
            F[j] = (F[j] + 1ll * mu[i] * f[j / i] % MOD * (j / i) % MOD + MOD) % MOD;
        }
    }
    for (int i = 1; i < N; i++) {
        F[i] = 1ll * F[i] * qpow(i, 1ll * k * x) % MOD;
        G[i] = (G[i - 1] + F[i]) % MOD;
    }
    for (int i = 1; i < N; i++) {
        val[i] = (val[i - 1] + qpow(i, k)) % MOD;
        S[i] = qpow(val[i], x);
    }
    while (T--) {
        int n;
        cin >> n;
        int ans = 0;
        for (int l = 1, r; l <= n; l = r + 1) {
            r = n / (n / l);
            ans = (ans + 1ll * S[n / l] * (G[r] - G[l - 1] + MOD) % MOD) % MOD;
        }
        cout << ans << '\n';
    }
}
int main() {
#ifdef Local
    freopen("input.in", "r", stdin);
#endif
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cout << fixed << setprecision(20);
    init();
    run();
    return 0;
}

H. Yukikaze and Smooth numbers

題意:
定義一個\(k-number\)為所有的質因子大小都不超過\(k\)的數。
現在問\(1\)~\(n\)中有多少個\(k-number\)

思路:
趣題,考察對min25篩的理解。
考慮通過min25篩直接篩出符合條件的數的個數。
仔細思考min25篩的第二部分,我們是分兩種情況統計,質數和合數,質數直接利用預處理得到的資訊計算,合數則是列舉遞迴下去(完全積性)。合數部分是不變的,一般只需要修改質數部分,這裡我們直接把大於k的去掉就行了。
因為要統計字首質數個數,所以第一部分就預處理質數個數和就行。
細節見程式碼:

Code
// Author : heyuhhh
// Created Time : 2020/08/07 16:50:26
#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;
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
//head
const int N = 1e5 + 5;

ll n, k;
ll sum1[N], sum2[N], prime[N];
ll w[N], ind1[N], ind2[N];
ll g1[N], g2[N];
bool chk[N];
int tot, cnt;
void pre(int n) { //  \sqrt
    chk[1] = 1;
    for(int i = 1; i <= n; i++) {
        if(!chk[i]) {
            prime[++tot] = i;
            sum1[tot] = sum1[tot - 1] + 1;
        }
        for(int j = 1; j <= tot && prime[j] * i <= n; j++) {
            chk[i * prime[j]] = 1;
            if(i % prime[j] == 0) break;
        }
    }
}

int f(int x, int y) {
    return x <= k;
}

void calc_g(ll n) {
    int z = sqrt(n + 0.5);
    for(ll i = 1, j; i <= n; i = j + 1) {
        j = n / (n / i);
        w[++cnt] = n / i;
        
        g1[cnt] = w[cnt] - 1;

        if(n / i <= z) ind1[n / i] = cnt;
        else ind2[n / (n / i)] = cnt;
    }
    for(int i = 1; i <= tot; i++) {
        for(int j = 1; j <= cnt && prime[i] * prime[i] <= w[j]; j++) {
            ll tmp = w[j] / prime[i], k;
            if(tmp <= z) k = ind1[tmp]; else k = ind2[n / tmp];
            g1[j] -= (g1[k] - sum1[i - 1]); 
        }
    }
}

ll num;

ll S(ll x, int y) { // 2~x >= P_y
    if(x <= 1 || prime[y] > x) return 0;
    int z = sqrt(n + 0.5);
    ll ans;
    if (k <= x) {
        ans = num - sum1[y - 1];
    } else {
        ll t = (x <= z ? ind1[x] : ind2[n / x]);
        ans = g1[t] - sum1[y - 1];
    }        
    ans = max(ans, 0ll);
    ll tmp = ans;
    for(int i = y; i <= tot && prime[i] * prime[i] <= x ; i++) {
        ll pe = prime[i];
        for(int e = 1; pe * prime[i] <= x; ++e, pe = pe * prime[i]) {
            ans += f(prime[i], e) * S(x / pe, i + 1) + f(prime[i], e + 1);
        }
    }
    return ans;
} 

void run() {
    tot = cnt = 0;
    memset(chk, 0, sizeof(chk));
    cin >> n >> k;
    int z = sqrt(k + 0.5);
    pre(z);
    calc_g(k);
    num = g1[ind2[1]];

    tot = cnt = 0;
    memset(chk, 0, sizeof(chk));
    z = sqrt(n + 0.5);
    pre(z);
    calc_g(n);
    cout << S(n, 1) + 1 << '\n';
}
int main() {
#ifdef Local
    freopen("input.in", "r", stdin);
#endif
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cout << fixed << setprecision(20);
    int T; cin >> T; while(T--)
    run();
    return 0;
}

I. Divisibility

簽到。

J. Expectation

根據期望的線性性質,可以直接對二進位制拆位然後求每一位對答案的貢獻。
那麼對於每一位找出所有這一位為\(1\)的邊然後跑個生成樹計數即是有貢獻的所有情況,總情況也是對於所有邊跑個生成樹計數。
那麼概率易求,乘以權值就是當前這一位對答案的貢獻。
注意鄰接矩陣是廣義的鄰接矩陣。

Code
// Author : heyuhhh
// Created Time : 2020/08/06 13:38:39
#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 = 100 + 5, M = 10000 + 5, MOD = 998244353;

int qpow(ll a, ll b) {
    ll res = 1;
    while(b) {
        if (b & 1) res = res * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return res;
}

int n, m;

ll b[N][N];
int g[N][N];
ll Det(int n){
    int i,j,k;
    ll ret = 1;
    for(i=2;i<=n;i++){
        for(j = i+1;j <= n;j++){
            while(b[j][i]){
                ll tmp=b[i][i]/b[j][i];//不存在除不盡的情況
                for(k = i;k <= n;k++){
                    b[i][k] = (b[i][k] - tmp*b[j][k])%MOD;
                    if(b[i][k]<0) b[i][k]+=MOD;
                }
                swap(b[i],b[j]);
                ret = -ret;
            }
        }
        if(!b[i][i]) return -1;
        ret = ret * b[i][i]%MOD;
    }
    if(ret < 0) ret += MOD;
    return ret;
}

int f[N];
int find(int x) {
    return f[x] == x ? f[x] : f[x] = find(f[x]);
}

bool merge(int x, int y) {
    int fx = find(x), fy = find(y);
    if (fx != fy) {
        f[fx] = fy;
        return true;
    }
    return false;
}

void run() {
    cin >> n >> m;
    vector<pair<pii, int>> edges;
    memset(b, 0, sizeof(b));
    memset(g, 0, sizeof(g));
    for (int i = 1; i <= m; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        edges.push_back(MP(MP(u, v), w));
        ++g[u][v], ++g[v][u];
        ++b[u][u], ++b[v][v];
        --b[u][v], --b[v][u];
    }
    int ans = 0;
    ll tot = Det(n);
    int fm = qpow(tot, MOD - 2);
    for (int k = 30; k >= 0; k--) {
        memset(b, 0, sizeof(b));
        memset(g, 0, sizeof(g));
        for (int i = 1; i <= n; i++) {
            f[i] = i;
        }
        int cnt = 0;
        for (auto& it : edges) {
            int u = it.fi.fi, v = it.fi.se, w = it.se;
            if (w >> k & 1) {
                ++g[u][v], ++g[v][u];
                ++b[u][u], ++b[v][v];
                --b[u][v], --b[v][u];
                if (merge(u, v)) ++cnt;
            }
        }
        if (cnt < n - 1) continue;
        ll q = Det(n);
        if (q == -1) continue;
        int res = 1ll * (1 << k) * q % MOD * fm % MOD;
        ans = (ans + res) % MOD;
    }
    cout << ans << '\n';
}
int main() {
#ifdef Local
    freopen("input.in", "r", stdin);
#endif
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cout << fixed << setprecision(20);
    int T; cin >> T; while(T--)
    run();
    return 0;
}