1. 程式人生 > 實用技巧 >AGC002D——F

AGC002D——F

AGC002

D

多次詢問,整體二分。具體一些,當前二分的區間為 \(l,r\),先設定答案為 \(mid\),判斷是否可行,可行的往 \([l,mid]\) 遞迴,否則走 \([mid + 1, r]\)。關鍵在於如何快速判定是否可行。

用並查集維護連通塊的大小,一種辦法使用可回退並查集。還有一種是按照 \(bfs\) 順序處理區間(按層處理)。這樣只要在每層開頭暴力清空,然後按照順序連

#include <bits/stdc++.h>
using namespace std;
void read (int &x) {
    char ch = getchar(); x = 0; while (!isdigit(ch)) ch = getchar();
    while (isdigit(ch)) x = x * 10 + ch - 48, ch = getchar();
} const int N = 1e5 + 5;
int n, m, q, res[N];
struct query { int x, y, z; } a[N];
struct edge { int x, y; } e[N];
int fa[N], sz[N];
int find (int x) { return fa[x] == x ? x : fa[x] = find (fa[x]); }
void merge (int x, int y) {
    int fx = find (x), fy = find (y);
    if (sz[fx] > sz[fy]) swap (fx, fy);
    if (fx ^ fy) fa[fx] = fy, sz[fy] += sz[fx];
}
int cnt, ll[N << 2], rr[N << 2], tag[N << 2];
vector<int> g[N << 2];
#define ls (p << 1)
#define rs (p << 1 | 1)
void build (int p, int l, int r) {
    ll[p] = l, rr[p] = r, tag[p] = 1;
    if (l == r) return; int mid (l + r >> 1);
    build (ls, l, mid), build (rs, mid + 1, r);
}
void solve () {
    for (int p = 1; p <= (n << 2); ++p) {
        if (!tag[p]) continue;
        // printf ("%d %d\n", ll[p], rr[p]);
        if (ll[p] == 1) {
            for (int i = 1; i <= n; ++i) fa[i] = i, sz[i] = 1;
        }
        if (ll[p] == rr[p]) {
            merge (e[ll[p]].x, e[ll[p]].y);
            for (int i : g[p]) res[i] = ll[p]; continue;
        }
        int mid (ll[p] + rr[p] >> 1);
        for (int i = ll[p]; i <= mid; ++i) merge (e[i].x, e[i].y);
        for (int i : g[p]) {
            int x = a[i].x, y = a[i].y, z = a[i].z;
            x = find (x), y = find (y);
            int s = (x == y) ? sz[x] : sz[x] + sz[y];
            // printf ("%d %d %d\n", i, s, z);
            s >= z ? g[ls].push_back (i) : g[rs].push_back (i);
        }
        for (int i = mid + 1; i <= rr[p]; ++i) merge (e[i].x, e[i].y);
    }
}
signed main() {
    read (n), read (m);
    for (int i = 1; i <= m; ++i) read (e[i].x), read (e[i].y);
    read (q);
    for (int i = 1; i <= q; ++i)
        read (a[i].x), read (a[i].y), read (a[i].z);
    for (int i = 1; i <= q; ++i) g[1].push_back (i);
    build (1, 1, m); solve ();
    for (int i = 1; i <= q; ++i) printf ("%d\n", res[i]);
    return 0;
}

E

剽三張圖助於理解。把所有石堆排序扔到座標軸上後事情就很明顯了。兩種操作分別代表向上走一步和向右走一步。最外圍一圈的勝負狀態確定。

可以發現,先手可以走到任意的 \((x,y),|x-y|\leq1\),後手可以控制 \(|x-y|\leq1\),所以只要判斷是否存在這樣的 \((x,y)\) 讓先手勝,不勝則敗。

#include <bits/stdc++.h>
using namespace std;
#define int long long
void read (int &x) {
    char ch = getchar(); x = 0; while (!isdigit(ch)) ch = getchar();
    while (isdigit(ch)) x = x * 10 + ch - 48, ch = getchar();
} const int N = 1e5 + 5;
int n, tag, a[N];
void work () {
    for (int i = 1; i <= n; ++i) {
        int j = i + 1;
        while (a[i] == a[j] && j <= n) ++j; --j;
        int l = i, r = j;
        if ((r - a[i]) & 1) {
            if (l <= a[i] - 1 && a[i] - 1 <= r) { tag = 1; break; }
            if (l <= a[i] + 1 && a[i] + 1 <= r) { tag = 1; break; }
        } i = j;
        l = a[i + 1] + 1, r = a[i];
        if (((r - i) & 1)) {
            if (l <= i - 1 && i - 1 <= r) { tag = 1; break; }
            if (l <= i + 1 && i + 1 <= r) { tag = 1; break; }
        }
    }
}
signed main() {
    read (n);
    for (int i = 1; i <= n; ++i) read (a[i]);
    sort (a + 1, a + n + 1), reverse (a + 1, a + n + 1);
    work ();
    puts (tag ? "First" : "Second");
    return 0;
}

F

因為每種顏色的求會有一個白色,所以可以重新把球分類為白球和其他顏色,這兩種球相對獨立

這就是狀態了:\(f_{i,j}\) 表示當前填了 \(i\) 個白球,\(j\) 類其他顏色的球的方案數 (其他顏色都是一下全部填完)

轉移依據也比較奇特:按照當前空位最左邊填什麼轉移。這樣可以保證白球的合法性

如果填白的直接轉,否則乘上一個組合數。當然其他顏色種類不能大於白球數量。組合數具體是什麼不難想,就不寫了(懶)

#include <bits/stdc++.h>
using namespace std;
#define int long long
void read (int &x) {
    char ch = getchar(); x = 0; while (!isdigit(ch)) ch = getchar();
    while (isdigit(ch)) x = x * 10 + ch - 48, ch = getchar();
} const int N = 2010, mod = 1e9 + 7;
int n, k, f[N][N], pw[N * N], in[N * N];
int qpow (int x, int y) {
    int t = 1;
    while (y) {
        if (y & 1) t = t * x % mod;
        x = x * x % mod, y >>= 1;
    } return t;
}
int C (int x, int y) {
    // if (x < y) return 0;
    return pw[x] * in[y] % mod * in[x - y] % mod;
}
signed main() {
    read (n), read (k); f[0][0] = pw[0] = 1;
    for (int i = 1; i <= n * k; ++i) pw[i] = pw[i - 1] * i % mod;
    in[n * k] = qpow (pw[n * k], mod - 2);
    for (int i = n * k; i >= 1; --i) in[i - 1] = in[i] * i % mod;
    if (k == 1) return puts ("1"), 0;
    for (int i = 1; i <= n; ++i)
        for (int j = 0; j <= i; ++j) {
            f[i][j] = f[i - 1][j];
            if (j) (f[i][j] += f[i][j - 1] * (n - j + 1) % mod * C (n * k - i - (j - 1) * (k - 1) - 1, k - 2)) %= mod;
        }
    return printf ("%lld\n", f[n][n]), 0;
}