2023牛客寒假演算法基礎集訓營6 A-L

空白菌發表於2023-02-09

比賽連結

A

題解

知識點:模擬。

如題。

程式碼

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int x;
    cin >> x;
    if (x <= 7) cout << "very easy" << '\n';
    else if (x <= 233) cout << "easy" << '\n';
    else if (x <= 10032) cout << "medium" << '\n';
    else if (x <= 114514) cout << "hard" << '\n';
    else if (x <= 1919810) cout << "very hard" << '\n';
    else cout << "can not imagine" << '\n';
    return 0;
}

B

題解

知識點:因數集合,列舉,二分。

預處理 \([1,2 \times 10^5]\) 所有數的因數,設 \(pos_i\) 為因數 \(i\) 出現的位置,對每個數處理即可。

查詢 \(x\) 時,只需在 \(pox_{a_x}\) 內二分查詢大於 \(x\) 的位置,然後就可以得到 \(a_x\) 作為因數出現的位置個數。

時間複雜度 \(O((2 \times 10^5) \log ({2 \times 10^5})+(n+q) \sqrt {a_i})\)

空間複雜度 \(O((2 \times 10^5) \log ({2 \times 10^5})+(n+q) \sqrt {a_i})\)

程式碼

#include <bits/stdc++.h>
using namespace std;
using ll = long long;


int a[400007];
vector<int> pos[200007];
vector<int> factor[200007];
void get_factor(int n) {
    for (int i = 1;i <= n;i++)
        for (int j = i;j <= n;j += i)
            factor[j].push_back(i);
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    get_factor(2e5);
    int n, q;
    cin >> n >> q;
    for (int i = 1;i <= n;i++) cin >> a[i];
    for (int i = 1;i <= n;i++) for (auto f : factor[a[i]]) pos[f].push_back(i);

    while (q--) {
        int op, x;
        cin >> op >> x;
        if (op == 1) {
            a[++n] = x;
            for (auto f : factor[a[n]]) pos[f].push_back(n);
        }
        else {
            int id = lower_bound(pos[a[x]].begin(), pos[a[x]].end(), x) - pos[a[x]].begin();
            cout << pos[a[x]].size() - id - 1 << '\n';
        }
    }
    return 0;
}

C

題解

知識點:貪心。

手動模擬一下發現係數是二項式係數,大的數放中間小的放外邊即可。

時間複雜度 \(O(n^2)\)

空間複雜度 \(O(n^2)\)

程式碼

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

const int mod = 1e9 + 7;

int a[1007];
int s[1007];
int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n;
    cin >> n;
    for (int i = 1;i <= (n + 1) / 2;i++) a[i] = 2 * i - 1;
    for (int i = 1;i <= n / 2;i++) a[n - i + 1] = 2 * i;

    for (int i = 1;i <= n;i++) s[i] = a[i];
    for (int i = 1;i <= n - 1;i++) {
        for (int j = 1;j <= n - i;j++) {
            (s[j] += s[j + 1]) %= mod;
        }
    }
    cout << s[1] << '\n';
    for (int i = 1;i <= n;i++) cout << a[i] << " \n"[i == n];
    return 0;
}

D

題解

知識點:字串,線性dp。

為了容易求出去掉一個字母后的子序列個數,考慮分別dp字首和字尾子序列 t = "udu" 個數。

\(f_{i,j}\)\(s[1,i]\) 中子序列 \(t[1,j]\) 的個數, \(g_{i,j}\)\(s[n-i+1,n]\) 中子序列 \(t[3-j+1,3]\) 的個數,特別地 \(j = 0\) 時為空串。轉移方程很顯然,詳見程式碼。

最後列舉 \(n\) 個位置,第 \(i\) 個位置刪去後子序列總數為 \(\sum_{j = 0}^3 (f_{i - 1,j} \cdot g_{n - i,3 - j})\) 取最小值的位置即可。

時間複雜度 \(O(n)\)

空間複雜度 \(O(n)\)

程式碼

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

ll f[200007][4];
ll g[200007][4];
int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    string s;
    cin >> s;
    int n = s.size();
    s = "?" + s;
    string t = "?udu";
    f[0][0] = 1;
    for (int i = 1;i <= n;i++) {
        for (int j = 0;j <= 3;j++) {
            f[i][j] = f[i - 1][j];
            if (s[i] == t[j]) f[i][j] += f[i - 1][j - 1];
        }
    }
    reverse(s.begin() + 1, s.end());
    reverse(t.begin() + 1, t.end());
    g[0][0] = 1;
    for (int i = 1;i <= n;i++) {
        for (int j = 0;j <= 3;j++) {
            g[i][j] = g[i - 1][j];
            if (s[i] == t[j]) g[i][j] += g[i - 1][j - 1];
        }
    }
    reverse(s.begin() + 1, s.end());
    reverse(t.begin() + 1, t.end());
    int pos = -1;
    ll mn = 1e18;
    for (int i = 1;i <= n;i++) {
        ll tmp = 0;
        for (int j = 0;j <= 3;j++) tmp += f[i - 1][j] * g[n - i][3 - j];
        if (tmp < mn) mn = tmp, pos = i;
    }
    s[pos] = 'a';
    cout << s.substr(1, n) << '\n';
    return 0;
}

E

題解

知識點:數論,最小生成樹。

根據Kruskal最小生成樹,我們貪心選權小的邊即可。接下來對每個數 \(i\) 分類討論:

  1. \(1 + k < i\) 時,我們有 \(\gcd(1,i) = 1\) 權值是最小的。
  2. \(i+k\geq n\) 時, \(i\) 不存在 \(\gcd\) 的邊,因此我們選 \(\text{lcm}\) 最小的邊 \(\text{lcm}(1,i) = i\)
  3. \(i+k<n\) 時,我們列舉數 \(j \in [i+k+1,n]\) ,取 \(\gcd(i,j)\) 的最小值。注意到, \(2\times 10^5\) 內素數間距不會很大,因此不用列舉多少個數即可得到 \(\gcd(i,j) = 1\) ,此時跳出即可。

時間複雜度 \(O(n)\)

空間複雜度 \(O(1)\)

程式碼

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n, k;
    cin >> n >> k;
    ll ans = 0;
    for (int i = 2;i <= n;i++) {
        if (1 + k < i) {
            ans++;
            continue;
        }
        int mi = i;
        for (int j = i + k + 1;j <= n;j++) {
            mi = min(mi, gcd(i, j));
            if (mi == 1) break;
        }
        ans += mi;
    }
    cout << ans << '\n';
    return 0;
}

F

題解

方法一

知識點:列舉,優先佇列,離線,貪心。

離線預處理每次操作後的答案,每次對最大的數操作。

當所有數都為 \(1\) 時就不用繼續操作了,一個數最多操作 \(4\) 次,因此複雜度是線性的。

時間複雜度 \(O(n \log n + q)\)

空間複雜度 \(O(n)\)

方法二

知識點:列舉,優先佇列,離線,貪心,二分。

注意到每次操作後數字必然 \(\leq 31\) ,我們預處理答案為 \([1,31]\) 所需的操作次數,每次操作都對最大的數取。

對於一個詢問 \(k\) ,只需要二分找到小於等於 \(k\) 的第一個答案即可。

\(k\) 小於答案 \(31\) 的操作次數,說明有數沒有被操作到,我們對 \(a_i\) 從大到小排序取第 \(k+1\) 大的數即答案。

時間複雜度 \(O(n\log n + q)\)

空間複雜度 \(O(n)\)

程式碼

方法一

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

int f(int x) { return __builtin_popcount(x); }

int ans[200007 * 4];
int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n, q;
    cin >> n >> q;
    priority_queue<int> pq;
    for (int i = 1, x;i <= n;i++) {
        cin >> x;
        pq.push(x);
    }
    int cnt = 0;
    while (pq.size() && pq.top() > 1) {
        int mx = f(pq.top());
        pq.pop();
        pq.push(mx);
        ans[++cnt] = pq.top();
    }
    while (q--) {
        int k;
        cin >> k;
        cout << ans[min(cnt, k)] << '\n';
    }
    return 0;
}

方法二

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

int f(int x) { return __builtin_popcount(x); }
int a[200007];
int ans[40];

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n, q;
    cin >> n >> q;
    priority_queue<int> pq;
    for (int i = 1, x;i <= n;i++) {
        cin >> a[i];
        pq.push(a[i]);
    }
    int cnt = 0;
    for (int i = 31;i >= 1;i--) {
        while (pq.size() && pq.top() > i) {
            int mx = f(pq.top());
            pq.pop();
            pq.push(mx);
            cnt++;
        }
        ans[i] = cnt;
    }
    sort(a + 1, a + n + 1, greater<int>());
    while (q--) {
        int k;
        cin >> k;
        if (k < ans[31]) cout << a[k + 1] << '\n';
        else cout << lower_bound(ans + 1, ans + 31 + 1, k, greater<int>()) - ans << '\n';
    }
    return 0;
}

G

題解

知識點:貪心,雙指標。

顯然每次取最小的兩個負數,或者取最大的兩個正數乘在一起,這樣最大。

考慮從小到大排序,用雙指標指向最小的兩個數和最大的兩個數,每次取這兩組較大值加入答案。

時間複雜度 \(O(n)\)

空間複雜度 \(O(n)\)

程式碼

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

ll a[200007];
int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n, k;
    cin >> n >> k;
    for (int i = 1;i <= n;i++) cin >> a[i];
    sort(a + 1, a + n + 1);
    int l = 1, r = n;
    ll ans = 0;
    while (k && l < r) {
        ll x = a[l] * a[l + 1];
        ll y = a[r - 1] * a[r];
        if (x >= y) ans += x, l += 2;
        else ans += y, r -= 2;
        k--;
    }
    cout << ans << '\n';
    return 0;
}

H

題解

知識點:數學。

\([1,x-1]\)\([l,r]\) 的交叉部分算機率即可。

時間複雜度 \(O(1)\)

空間複雜度 \(O(1)\)

程式碼

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int x, l, r;
    cin >> x >> l >> r;
    double ans = (min(r, max(l - 1, x - 1)) - l + 1.0) / (r - l + 1);
    cout << fixed << setprecision(10) << ans << '\n';
    return 0;
}

I

題解

知識點:BFS,貪心。

一個結論就是,你走過的路可以立刻銷燬,並讓下一條你走的路變 \(1\) 。因此,對於一條路徑,只需要考慮起點出發的第一條路徑能否變成 \(1\) 即可,後續的路都可以透過銷燬前面的路變 \(1\)

我們先處理出起點到終點需要走多少條路,因此bfs時把邊權都當 \(1\) 遍歷一遍即可。如果到終點的需要走的路數量小於總邊數,那麼第一條路可以銷燬其他路變 \(1\) ,否則第一條路不能變。

時間複雜度 \(O(n+m)\)

空間複雜度 \(O(n+m)\)

程式碼

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

vector<pair<int, int>> g[200007];

bool vis[200007];
int dis[200007];
queue<int> q;
void bfs(int s) {
    q.push(s);
    vis[s] = 1;
    while (q.size()) {
        int u = q.front();
        q.pop();
        for (auto [v, w] : g[u]) {
            if (vis[v]) continue;
            vis[v] = 1;
            dis[v] = dis[u] + 1;
            q.push(v);
        }
    }
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n, m;
    cin >> n >> m;
    for (int i = 1;i <= m;i++) {
        int u, v, w;
        cin >> u >> v >> w;
        g[u].push_back({ v,w });
        g[v].push_back({ u,w });
    }
    bfs(1);
    if (m > dis[n]) cout << dis[n] << '\n';
    else cout << dis[n] - 1 + g[1].front().second << '\n';
    return 0;
}

J

題解

知識點:模擬,貪心,離線。

顯然我們需要離線儲存所有事件,事件分為三類:

  1. 刷題,pro中某人加 \(w\)
  2. 更新,rank更新為當前pro。
  3. 查詢,查詢rank中的某個人。

更新事件分為兩類:間隔為 \(T\) 的全部更新,查詢後的單人更新。注意到,全部更新並不需要更新所有人的rank,我們只需要在 \((k+1)T\) 時刻,更新 \((kT,(k+1)T]\) 這段時間刷過題,即pro改變的人,的rank即可,如此全部更新事件就變成單人更新了。

時間複雜度 \(O(n \log n)\)

空間複雜度 \(O(n)\)

程式碼

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

struct node {
    int when, what, name, w;
    friend bool operator<(const node &a, const node &b) {
        return a.when == b.when ? a.what < b.what : a.when < b.when;
    }
};
int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n, T, R;
    cin >> n >> T >> R;
    int idx = 0;
    map<string, int> mp;
    vector<node> v;
    for (int i = 1;i <= n;i++) {
        int op, t;
        string name;
        cin >> op >> t >> name;
        if (!mp.count(name)) mp[name] = ++idx;
        if (op == 1) {
            v.push_back({ t,3,mp[name],i });
            v.push_back({ t + R,2,mp[name],0 });
        }
        else {
            int w;
            cin >> w;
            v.push_back({ t,1,mp[name],w });
            v.push_back({ (t + T - 1) / T * T,2,mp[name],0 });
        }
    }
    sort(v.begin(), v.end());
    vector<ll> pro(idx + 1), rank(idx + 1);
    vector<pair<int, ll>> ans;
    for (auto val : v) {
        if (val.what == 1) pro[val.name] += val.w;
        else if (val.what == 2) rank[val.name] = pro[val.name];
        else ans.push_back({ val.w,rank[val.name] });
    }
    sort(ans.begin(), ans.end());
    for (auto val : ans) cout << val.second << '\n';
    return 0;
}

K

題解

知識點:博弈論,dfs。

考慮dfs搜尋操作,邊界條件為:

  1. 若超過了全域性最大值 \(2x\) ,則一定平局。
  2. 若上一輪的人走到了 \(0\) 或之前走到過的點,那麼這一輪看作勝。

轉移方式為:

  1. 若能讓下一輪必敗,那這一輪必勝。
  2. 否則,若能讓下一輪平局,則這一輪平局。
  3. 否則,這一輪必敗。

先搜尋除以 \(2\) 的分支,因為收斂更快,需要剪枝不然會超時。

用記憶化搜尋也可以寫,狀態為 \((l,x,r)\) 表示 \([l,x]\) 已經訪問過且大於 \(x\) 第一個被訪問過的點為 \(r\) 。但實際上沒必要,因為可以推斷同一個狀態最多被訪問兩次,不記憶還好寫點。

狀態空間為 \(O(x^3)\) ,但實際上根本跑不滿。

時間複雜度 \(O(\text{玄學})\)

空間複雜度 \(O(x)\)

程式碼

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

int mx;
bool vis[1007];
int dfs(int x) {
    if (x >= mx) return 0;
    if (x == 0 || vis[x]) return 1;
    vis[x] = 1;
    int l = dfs(x / 2);
    if (l == -1) return vis[x] = 0, 1;
    int r = dfs(x + 1);
    if (r == -1) return vis[x] = 0, 1;
    vis[x] = 0;
    return r ? -1 : 0;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int x;
    cin >> x;
    mx = x * 2;
    int f = dfs(x);
    if (f == 1) cout << "ning" << '\n';
    else if (f == -1) cout << "red" << '\n';
    else cout << "draw" << '\n';
    return 0;
}

L

題解

方法一

知識點:容斥原理,排列組合。

容斥原理是最直接的思路。

先有兩個結論:

  1. \(A(x_1,y_1)\)\(B(x_2,y_2)\) ,即 \(A \to B\) 的路徑總數是 \(\dbinom{x_2-x_1 + y_2 - y_1}{x_2-x_1}\)
  2. \(A(x,y)\) 到斜邊各點,即 \(A \to fin\) 的路徑總數是 \(2^{n-x+1-y}\) ,可以透過二項式係數證明。

我們需要 \((1,1) \to fin\) 且不經過禁用點的路徑總數,因此我們可以考慮 \((1,1) \to fin\) 路徑總數減去經過禁用點的路徑總數。

路徑總數很好求 \(2^{n-1}\)

對於只經過一個禁用點 \((1,1) \to (x,y) \to fin\) 的路徑數,我們可以透過 \((1,1) \to (x,y)\) 路徑數乘以 \((x,y) \to fin\) 的路徑數,利用結論1和2即可求得,結果是 \(\dbinom{x-1 + y - 1}{x-1} \cdot 2^{n-x+1-y}\)

但是,禁用點可能會有路徑包含關係。若 \(A(x_1,y_1)\)\(B(x_2,y_2)\) 滿足 \(x_1 < x_2\)\(y_1 <y_2\) ,則稱 \(A\) 包含 \(B\) 。對於 \(A\) 包含 \(B\) 的情況, \((1,1) \to A \to fin\) 的路徑可能也透過了 \(B\) ,因此計算 \((1,1) \to A \text{ 或 } B \to fin\) 的路徑總數時,需要先求出 \((1,1) \to A \to fin\)\((1,1) \to B \to fin\) 的路徑數,再減去 \((1,1) \to A \to B \to fin\) 的路徑數。

以此類推,我們列舉所有 \(2^m\) 個選禁用點透過的合法方案的路徑數,根據容斥原理,奇數個點的路徑加,偶數個點的路徑減,最後就可以得到經過禁用點的路徑總數。其中合法方案指,需要存在路徑能透過所有選中的禁用點,不能出現一條路徑沒經過某個點的情況,這要保證任意兩點都有包含關係。例如對於 \(A,B,C\),若 \(A\) 包含 \(B,C\)\(B\)\(C\) 沒用包含關係,那麼我們無法找到一條能同時透過 \(A,B,C\) 的路徑,所以這是不合法的。

為了方便,我們對點按照 \(x\) 為第一關鍵字, \(y\) 為第二關鍵字從小到大排序。

時間複雜度 \(O(2^mm)\)

空間複雜度 \(O(m)\)

方法二

知識點:拓撲序dp,排列組合。

我們不難發現,我們容斥中 \(2^m\) 次的加加減減,最後得到的實際上是到達每個禁用點實際路徑數,再乘以各自到達斜邊各點的路徑數,最後求和便是路徑總數。

同時,我們還能發現,實際上 \(2^m\) 次方案有很多重複計算。例如我們要求出到達 \(v\) 的實際路徑數,我們只需要知道所有 \(v\) 的前驅 \(u_i\) 的實際路徑數,然後用 \((1,1)\)\(v\) 的全部路徑數減去 \((1,1)\) 透過 \(u_i\)\(v\) 的路徑數和,即 \(((1,1) \to v) - \sum u_i (u_i \to v)\) ,可以得到 \(v\) 的實際路徑數。

注意到,這是一個可以遞推的過程,免去了容斥列舉的複雜度。因此,我們可以 \(O(m^2)\) 建一個DAG,設 \(f_u\) 為到 \(u\) 的實際路徑數,利用拓撲序dp,計算方法同上即可。最後,對於 \(f_u\) ,那麼 \(f_u \cdot 2^{n-u_x + 1-u_y}\) 即經過 \(u\) 的實際路徑數。

時間複雜度 \(O(m^2)\)

空間複雜度 \(O(m^2)\)

方法三

知識點:線性dp,排列組合。

更進一步,我們發現建圖的過程中其實可以直接dp了,為了方便我們排好序直接線性dp即可。

時間複雜度 \(O(m^2)\)

空間複雜度 \(O(m)\)

程式碼

方法一

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

const int P = 1e9 + 7;
const int N = 2e5 + 7;
namespace CNM {
    int qpow(int a, ll k) {
        int ans = 1;
        while (k) {
            if (k & 1) ans = 1LL * ans * a % P;
            k >>= 1;
            a = 1LL * a * a % P;
        }
        return ans;
    }
    int fact[N], invfact[N];
    void init(int n) {
        fact[0] = 1;
        for (int i = 1;i <= n;i++) fact[i] = 1LL * i * fact[i - 1] % P;
        invfact[n] = qpow(fact[n], P - 2);
        for (int i = n;i >= 1;i--) invfact[i - 1] = 1LL * invfact[i] * i % P;
    }
    int C(int n, int m) {
        if (n == m && m == -1) return 1; //* 隔板法特判
        if (n < m || m < 0) return 0;
        return 1LL * fact[n] * invfact[n - m] % P * invfact[m] % P;
    }
}
using namespace CNM;
using pii = pair<int, int>;

int AtoB(pii A, pii B) {
    auto [x1, y1] = A;
    auto [x2, y2] = B;
    return C(x2 + y2 - x1 - y1, x2 - x1);
}

int n, m;
pii pos[20];
int f[20];

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n >> m;
    for (int i = 1;i <= m;i++) cin >> pos[i].first >> pos[i].second;
    sort(pos + 1, pos + m + 1);
    init(n);
    int ans = 0;
    for (int i = 0;i < (1 << m);i++) {
        pii pre = { 1,1 };
        auto &[px, py] = pre;
        bool ok = 1, flag = 0;
        int mul = 1;
        for (int j = 0;j < m;j++) {
            if (!(i & (1 << j))) continue;
            auto [x, y] = pos[j + 1];
            if (px > x || py > y) {
                ok = 0;
                break;
            }
            mul = 1LL * mul * AtoB(pre, pos[j + 1]) % P;
            pre = pos[j + 1];
            flag ^= 1;
        }
        if (!ok) continue;
        if (flag) (ans -= 1LL * mul * qpow(2, n - px + 1 - py) % P - P) %= P;
        else (ans += 1LL * mul * qpow(2, n - px + 1 - py) % P) %= P;
    }
    cout << ans << '\n';
    return 0;
}

方法二

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

const int P = 1e9 + 7;
const int N = 2e5 + 7;
namespace CNM {
    int qpow(int a, ll k) {
        int ans = 1;
        while (k) {
            if (k & 1) ans = 1LL * ans * a % P;
            k >>= 1;
            a = 1LL * a * a % P;
        }
        return ans;
    }
    int fact[N], invfact[N];
    void init(int n) {
        fact[0] = 1;
        for (int i = 1;i <= n;i++) fact[i] = 1LL * i * fact[i - 1] % P;
        invfact[n] = qpow(fact[n], P - 2);
        for (int i = n;i >= 1;i--) invfact[i - 1] = 1LL * invfact[i] * i % P;
    }
    int C(int n, int m) {
        if (n == m && m == -1) return 1; //* 隔板法特判
        if (n < m || m < 0) return 0;
        return 1LL * fact[n] * invfact[n - m] % P * invfact[m] % P;
    }
}
using namespace CNM;
using pii = pair<int, int>;

int n, m;
pii pos[20];

int AtoB(pii A, pii B) {
    auto [x1, y1] = A;
    auto [x2, y2] = B;
    return C(x2 + y2 - x1 - y1, x2 - x1);
}

vector<int> g[20];
int f[20];

int deg[20];
queue<int> q;
void toposort() {
    for (int i = 1;i <= m;i++) if (!deg[i]) q.push(i);
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        (f[u] += AtoB({ 1,1 }, pos[u])) %= P;
        for (auto v : g[u]) {
            (f[v] -= 1LL * f[u] * AtoB(pos[u], pos[v]) % P - P) %= P;
            deg[v]--;
            if (!deg[v]) q.push(v);
        }
    }
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n >> m;
    for (int i = 1;i <= m;i++) cin >> pos[i].first >> pos[i].second;
    for (int i = 1;i <= m;i++) {
        auto [x1, y1] = pos[i];
        for (int j = 1;j <= m;j++) {
            if (i == j) continue;
            auto [x2, y2] = pos[j];
            if (x2 <= x1 && y2 <= y1) {
                g[j].push_back(i);
                deg[i]++;
            }
        }
    }
    init(n);
    toposort();
    ll ans = qpow(2, n - 1);
    for (int i = 1;i <= m;i++) {
        auto [x, y] = pos[i];
        (ans -= 1LL * f[i] * qpow(2, n - x + 1 - y) % P - P) %= P;
    }
    cout << ans << '\n';
    return 0;
}

方法三

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

const int P = 1e9 + 7;
const int N = 2e5 + 7;
namespace CNM {
    int qpow(int a, ll k) {
        int ans = 1;
        while (k) {
            if (k & 1) ans = 1LL * ans * a % P;
            k >>= 1;
            a = 1LL * a * a % P;
        }
        return ans;
    }
    int fact[N], invfact[N];
    void init(int n) {
        fact[0] = 1;
        for (int i = 1;i <= n;i++) fact[i] = 1LL * i * fact[i - 1] % P;
        invfact[n] = qpow(fact[n], P - 2);
        for (int i = n;i >= 1;i--) invfact[i - 1] = 1LL * invfact[i] * i % P;
    }
    int C(int n, int m) {
        if (n == m && m == -1) return 1; //* 隔板法特判
        if (n < m || m < 0) return 0;
        return 1LL * fact[n] * invfact[n - m] % P * invfact[m] % P;
    }
}
using namespace CNM;
using pii = pair<int, int>;

int AtoB(pii A, pii B) {
    auto [x1, y1] = A;
    auto [x2, y2] = B;
    return C(x2 + y2 - x1 - y1, x2 - x1);
}

int n, m;
pii pos[20];
int f[20];

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n >> m;
    for (int i = 1;i <= m;i++) cin >> pos[i].first >> pos[i].second;
    sort(pos + 1, pos + m + 1);
    init(n);
    int ans = qpow(2, n - 1);
    for (int i = 1;i <= m;i++) {
        auto [x1, y1] = pos[i];
        f[i] = AtoB({ 1,1 }, pos[i]);
        for (int j = 1;j < i;j++) {
            auto [x2, y2] = pos[j];
            if (x2 <= x1 && y2 <= y1) (f[i] -= 1LL * f[j] * AtoB(pos[j], pos[i]) % P - P) %= P;
        }
        (ans -= 1LL * f[i] * qpow(2, n - x1 + 1 - y1) % P - P) %= P;
    }
    cout << ans << '\n';
    return 0;
}

相關文章