07-31 題解

Bubble_e發表於2024-08-01

07-31 題解

A

首先, 如果 \(k \ge m\) , 答案為 \(m\) , 因為每變換一次, 字首中多一個 1

想法

\(k\) 非常的大, 不可能計算出最終狀態

但是 \(m\) 非常小, 如果有一種 \(O(答案)\) 的做法就好了 (類似 NOI 2024 D2T1 的部分分做法)

重要的性質

​ 對於一棵樹, 如果每個父節點都有至少兩個兒子, 那麼總節點個數 \(\le 2*葉子個數\) (以二叉樹為例理解)

根據此性質, 如果我們暴搜只搜到 \(m\) 個葉子, 那麼複雜度接近 \(O(m)\)

為保證每個父節點至少兩個兒子, 需要把所有狀態為 1 的點剪掉 (''狀態'' 即當前 x)

完結 !

程式碼

#include <bits/stdc++.h>
#define int long long
#pragma optmistic(3)
using namespace std;
const int N = 1e6 + 10;
const int INF = 1e18;

int x, k, m;
int si, ans;
vector <int> temp;
unordered_map <int, bool> vis;
unordered_map <int, vector <int>> rec;

void Corner(){
    if(x == 1){
        cout << "1\n";
        exit(0);
    }
    if(k >= m){
        cout << m << "\n";
        exit(0);
    }
}

int tar;
unordered_map <int, bool> rpri;

void Get_prii(int i, int num){
    if(i == temp.size()){
        if(rpri[num]) return;
        rpri[num] = 1;
        rec[tar].push_back(num);
        return;
    }
	Get_prii(i + 1, num * temp[i]);
    Get_prii(i + 1, num);
}

void Get_pri(int x){
    temp.clear();
    rpri.clear();

    int xx = x;
    for(int i = 2; i * i <= x; i++){
        if(xx % i == 0){
            while(xx % i == 0){
                temp.push_back(i);
                xx /= i;
            }
        }
    }
    if(xx != 1){
        temp.push_back(xx);
    }

    tar = x;
    Get_prii(0, 1);

    sort(rec[x].begin(), rec[x].end());
}

void Dfs(int x, int dep){
    if(!si){
        cout << ans << "\n";
        // printf("%.10lf\n", 1.0 * clock() / CLOCKS_PER_SEC);
        exit(0);
    }
    if(x == 1){
        --si;
        ++ans;
        return;
    }
    if(dep == k - 1){
        if(!vis[x]){
            Get_pri(x);
            vis[x] = 1;
        }
        for(auto i : rec[x]){
            ans += i;
            --si;
            if(!si) break;
        }
        return;
    }
    if(!vis[x]){
        Get_pri(x);
        vis[x] = 1;
    }
    for(auto i : rec[x]){
        Dfs(i, dep + 1);
    }
    return;
}

void Calc1(){
    si = m;
    Dfs(x, 0);
    cout << ans << "\n";
    // printf("%.10lf\n", 1.0 * clock() / CLOCKS_PER_SEC);
}

signed main(){
    cin >> x >> k >> m;
    Corner();

    Calc1();
}

B

我們喜歡確定的東西, 不喜歡不確定的, 但是這題所有東西都不確定, 這性質非常好

由於都不確定, 所以除了被我們選中的數, 其他都是等價的 !

想出一個狀態, 用來表示一種路徑集合

\(f(l, r, 0/1)\) 表示在這條路徑中, 向右走了 \(l\) 步 (有 \(l\) 個比他小的 \(mid\)) , 向左走了 \(r\) 步 (\(r\) 個比他大的 \(mid\)), 中止位置 否/是 x 的路徑集合的大小

搜尋過程是類似二分的, 所以 \(l, r\) 都是 \(log\) 級別的

\(f(l, r, 0/1) = l! * r!\) 因為所有 大於/小於 當前 \(x\) 的數都是等價的

所以可以仿照題目做一次搜尋, 在搜尋過程中求出 \(f\)

再者, \(f\) 與當前數 \(x\) 的具體值無關, 只和相對大小有關, 這也是 ''序列完全不確定'' 帶來的性質

然後再求出從所有 小於/大於 當前 \(x\) 的數中選出 \(l/ r\) 個數的貢獻, 可以對 前/後 綴做 dp 求出

剩下的數隨便放就行, 對返回值不做貢獻, 有階乘中方案

形式化地

\[對於 f(l, r, 0) \]

\[ans = f(l, r, 0) * val * fac[n - l - r] \]

\[val 與 pre[x - 1][l], suf[x + 1][r] 有關 \]

\[對於 f(l, r, 1) \]

\[ans = f(l, r, 1) * val * fac[n - l - r - 1] (-1 是因為 x 這個數的位置已經確定)\\ \]

\[val 與 pre[x - 1][l], suf[x + 1][r] 有關 \]

程式碼

#include <bits/stdc++.h>
#define int long long
#pragma optmistic(3)
using namespace std;
const int N = 3e5 + 10, MOD = 1e9 + 7;

int n, m, x;
int num[20][20][2];

struct Node{
    int n, v;

    Node operator += (const Node &p){
        (n += p.n) %= MOD;
        (v += p.v) %= MOD;
        return {n, v};
    }

    Node operator += (const int &p){
        (v += n * p % MOD) %= MOD;
        return {n, v};
    }

    Node operator * (const Node &p){
        int nn = n * p.n % MOD;
        int vv = (n * p.v % MOD + v * p.n % MOD) % MOD;
        return {nn, vv};
    }
}pre[N][20], suf[N][20], mul;

int fac[N], inv[N];

void Init(){
    int up = 3e5;
    fac[0] = fac[1] = 1;
    inv[0] = inv[1] = 1;
    for(int i = 2; i <= up; i++){
        fac[i] = fac[i - 1] * i % MOD;
        inv[i] = (MOD - MOD / i) * inv[MOD % i] % MOD;
    }
    for(int i = 2; i <= up; i++){
        inv[i] = inv[i - 1] * inv[i] % MOD;
    }
}

int C(int n, int m){
    return fac[n] * inv[m] % MOD * inv[n - m] % MOD;
}

void Dfs(int l, int r, int cl, int cr){
    if(l > r){
        (num[cl][cr][0] += fac[cl] * fac[cr] % MOD) %= MOD;
        return;
    }
    int mid = (l + r) / 2;
    (num[cl][cr][1] += fac[cl] * fac[cr] % MOD) %= MOD;
    Dfs(l, mid - 1, cl, cr + 1);
    Dfs(mid + 1, r, cl + 1, cr);
}

signed main(){
    Init();

    cin >> n >> m;

    Dfs(1, n, 0, 0);

    pre[0][0].n = 1;
    for(int i = 1; i <= n; i++){
        for(int j = 0; j < 20; j++){
            if(j != 0){
                pre[i][j] = pre[i - 1][j - 1];
                pre[i][j] += i;
            }
            pre[i][j] += pre[i - 1][j];
            // cout << pre[i][j].n << "+" << pre[i][j].v << " ";
        }
    }
    // cout << "\n";

    suf[n + 1][0].n = 1;
    for(int i = n; i >= 1; i--){
        for(int j = 0; j < 20; j++){
            if(j != 0){
                suf[i][j] = suf[i + 1][j - 1];
                suf[i][j] += i;
            }
            suf[i][j] += suf[i + 1][j];
            // cout << suf[i][j].n << "+" << suf[i][j].v << " ";
        }
    }
    // cout << "Ed\n";

    for(int i = 1; i <= m; i++){
        cin >> x;
        int ans = 0;
        for(int l = 0; l < 20; l++){
            for(int r = 0; r < 20; r++){
                if(!num[l][r][0] && !num[l][r][1]){
                    continue;
                }

                mul = pre[x - 1][l] * suf[x + 1][r];
                // cout << mul.n << "+" << mul.v << " ";
                if(num[l][r][0]){
                    (ans += mul.v * num[l][r][0] % MOD * fac[n - l - r] % MOD) %= MOD;
                }                
                if(num[l][r][1]){
                    mul += x;
                    (ans += mul.v * num[l][r][1] % MOD * fac[n - l - r - 1] % MOD) %= MOD;
                }
            }
        }
        cout << ans << "\n";
    }
}

C

我第一眼的想法是做高維字首和, 該位置的取值在他的補集的子集中取最優的, 但這樣是 \(n^2\) 級別的

性質

​ 這種交換是具有傳遞性的, 即若 a 和 b 可以交換, b 和 c 可以交換, 那麼可以經過 b 中轉, 使得 a, c 互換

所以可以把能互換的點對之間連邊, 處於同一個連通塊的點可以貪心地賦值

但是這樣連邊也是 \(n^2\) 級別的

考慮用類似高維字首和的思路連邊, 即建立一些 ''源點'' 減少連邊數量

每個點向他的補集連邊, 每個點所在集合向他連邊, 每個集合向比他在二進位制上少一個 1 的子集連邊

這樣, 原本處於同一連通塊中的點現在就處在同一個強連通分量中, 用 Tarjan 找一下強連通分量即可

程式碼

#include <bits/stdc++.h>
#define int long long
#pragma optmistic(3)
using namespace std;
const int N = 3e6 + 10, ST = (1 << 20) + 1;

int n, a[N];
vector <int> e[N];

int dfn[N], low[N], cdfn;
int col[N], ccol;
bool ins[N];
vector <int> st;

priority_queue <int, vector <int>, greater <int>> q[N];

void Tarjan(int x){
    dfn[x] = low[x] = ++cdfn;

    ins[x] = 1;
    st.push_back(x);

    for(auto i : e[x]){
        if(!dfn[i]){
            Tarjan(i);
            low[x] = min(low[x], low[i]);
        }else if(ins[i]){
            low[x] = min(low[x], dfn[i]);
        }
    }

    // cout << x << " " << low[x] << "+" << dfn[x] << "\n";
    if(dfn[x] == low[x]){
        ++ccol;

        int tp = st.back();
        do{
            // cout << tp << "tp";
            tp = st.back(), st.pop_back();
            ins[tp] = 0;
            col[tp] = ccol;
        }while(tp != x);
        // cout << "\n";
    }
}

signed main(){
    // freopen("1.in", "r", stdin);
    // freopen("1.out", "w", stdout);
    freopen("seq.in", "r", stdin);
    freopen("seq.out", "w", stdout);
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);

    cin >> n;
    for(int i = 1; i <= n; i++){
        cin >> a[i];
    }

    int up = (1 << 20) - 1, upp = 20;
    // cout << ST << "ST\n";
    for(int i = 1; i <= n; i++){
        int ed = (up ^ a[i]);
        e[i + ST].push_back(ed);
        e[a[i]].push_back(i + ST);
        // cout << i + ST << " " << ed << "\n";
        // cout << a[i] << " " << i + ST << "\n";
    }
    for(int i = 0; i <= up; i++){
        for(int j = 0; j < upp; j++){
            if((i >> j) & 1){
                e[i].push_back(i ^ (1 << j));
                // cout << i << " " << (i ^ (1ll << j)) << "\n";
            }
        }
    }

    for(int i = 0; i <= ST + n; i++){
        if(!dfn[i]){
            Tarjan(i);
        }
    }

    // for(int i = 0; i <= ST + n; i++){
    //     cout << col[i] << " ";
    // }
    // cout << "\n";

    for(int i = ST + 1; i <= ST + n; i++){
        // cout << col[i] << " ";
        q[col[i]].push(a[i - ST]);
    }
    // cout << "\n";

    for(int i = ST + 1; i <= ST + n; i++){
        cout << q[col[i]].top() << " ";
        q[col[i]].pop();
    }
    cout << "\n";
}

D

有個人寫了快一百行的 點分治, 就是為了拿 15 pts 的部分分, 我不說是誰

鴿了