20240703

forgotmyhandle發表於2024-07-03

T1

Topcoder SRM 632 div2 Hard - GoodSubset

直接記搜,不整除就不取當前數。由於給定數的因數個數是很少的,所以記搜完全跑得過去。

程式碼
#include <iostream>
#include <map>
using namespace std;
const int P = 1000000007;
map<pair<int, int>, int> vis;
int n;
int a[105];
int V;
void Madd(int& x, int y) { (x += y) >= P ? (x -= P) : 0; }
int dfs(int p, int v) {
    if (p == n + 1) 
        return v == 1;
    pair<int, int> pr = make_pair(p, v);
    if (vis.count(pr)) 
        return vis[pr];
    int ret = 0;
    if (v % a[p] == 0) 
        ret = dfs(p + 1, v / a[p]);
    Madd(ret, dfs(p + 1, v));
    return vis[pr] = ret;
}
int main() {
    cin >> V >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    cout << dfs(1, V) - (V == 1) << "\n";
    return 0;
}

T2

NFLSOJ P2845 數位和同餘

考慮類似於根號分治的東西。對於 \(P\) 比較小的情況,直接數位 dp,記錄前面位的數位和和原數對 \(P\) 的餘數。我寫的可以跑到 \(P = 10^4\)。對於剩下的情況,首先注意到數位和不大,在 \([0, 90]\),所以餘數也只能是這些。我們列舉數位和,每次給當前數加上 \(P\) 直到超過 \(M\) 為止,檢查每個數對 \(P\) 取模是否等於其數位和。複雜度 \(O(能過)\)

程式碼
#include <iostream>
#include <string.h>
#define int long long
using namespace std;
int P, M;
int dp[15][95][10005];
int d[15], dcnt;
int dfs(int pos, int x, int y, bool lim) {
    if (pos == 0) 
        return x == y;
    if (!lim && dp[pos][x][y] != -1) 
        return dp[pos][x][y];
    int ret = 0;
    int up = (lim ? d[pos] : 9);
    for (int i = 0; i <= up; i++) 
        ret += dfs(pos - 1, (x + i) % P, (y * 10 + i) % P, lim & (i == up));
    if (!lim) 
        dp[pos][x][y] = ret;
    return ret;
}
int calc(int x) {
    int ret = 0;
    while (x) ret += x % 10, x /= 10;
    return ret;
}
signed main() {
    memset(dp, -1, sizeof dp);
    cin >> P >> M;
    int tmp = M;
    while (M) d[++dcnt] = M % 10, M /= 10;
    if (P <= 10000) 
        cout << dfs(dcnt, 0, 0, 1) - 1 << "\n";
    else {
        int a1 = -1;
        for (int i = 0; i <= dcnt * 9; i++) {
            for (int j = i; j <= tmp; j += P) 
                a1 += (i == calc(j) % P);
        }
        cout << a1 << "\n";
    }
    return 0;
}

T3

Topcoder SRM 555 div1 Medium - XorBoard

先把所有操作中對同一行 / 列的兩次操作的去掉,設有 \(x\) 個行在最後被操作了,\(y\) 個列在最後被操作了。可以列出白色格子數量 \(S = xW + yH - 2xy\)。列舉 \(x\),可以解出 \(y\),然後對解出的 \(y\) 有一些合法性的要求。特別地,可能會解出無窮多解的情況,這是合法的。確定了行列各有多少在最後是被操作的,就可以計算方案數。先把這些行列操作掉,則還要求行和列的剩餘操作次數都是 \(2\) 的倍數。相當於把 \(x\) 個被操作的行分配到 \(H\) 個行裡,把剩下的很多對操作分配到每一行上。兩部分都是組合數,把行和列的方案乘起來最後求和即為答案。

程式碼
#include <iostream>
#define int long long
using namespace std;
const int P = 555555555;
int H, W, r, c, S;
int C[4005][4005];
void Madd(int& x, int y) { (x += y % P) >= P ? (x -= P) : 0; }
signed main() {
    cin >> H >> W >> r >> c >> S;
    C[0][0] = 1;
    for (int i = 1; i <= 4000; i++) {
        for (int j = 0; j <= 4000; j++) 
            j == 0 ? (C[i][j] = 1) : (C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % P);
    }
    int ans = 0;
    for (int x = 0; x <= H; x++) {
        int p = S - x * W;
        int q = H - 2 * x;
        if (q != 0 && p % q != 0) 
            continue;
        else if (q == 0 && p != 0) 
            continue;
        if (p == 0 && q == 0) {
            for (int y = 0; y <= W; y++) {
                if ((r - x < 0 || ((r - x) & 1)) || (c - y < 0 || ((c - y) & 1))) 
                    continue;
                int n1 = (r - x) >> 1;
                int n2 = (c - y) >> 1;
                Madd(ans, (C[H][x] * C[W][y] % P) * (C[n1 + H - 1][H - 1] * C[n2 + W - 1][W - 1] % P));
            }
        } else {
            int y = p / q;
            if (y < 0 || y > W) 
                continue;
            if ((r - x < 0 || ((r - x) & 1)) || (c - y < 0 || ((c - y) & 1))) 
                continue;
            int n1 = (r - x) >> 1;
            int n2 = (c - y) >> 1;
            Madd(ans, (C[H][x] * C[W][y] % P) * (C[n1 + H - 1][H - 1] * C[n2 + W - 1][W - 1] % P));
        }
    }
    cout << ans << "\n";
    return 0;
}

T4

Topcoder SRM 550 div1 Hard - ConversionMachine

考慮所有合法操作一定規約成先把原串變成合法,然後三個一組地對某個字母操作。考慮一個 \(dp[a][i][j][k]\) 表示操作 \(a\) 次後有 \(i\) 個字母剩 \(1\) 次操作正確,\(j\) 個剩 \(2\) 次,\(k\) 個已經正確的方案數。轉移考慮改哪種字元。由於 \(i + j + k = n\),最後一維可以去掉。注意到這裡沒有限制操作代價,是因為對代價的限制可以轉化為對操作次數的限制。根據前面的規約,我們可以算出合法的最小代價。然後三個一組的操作相當於每次給代價加上特定值。當代價超過限制時就不能操作了。所以對代價的限制本質上相當於對操作次數的限制。所以可以直接矩乘快速冪最佳化這個 dp。注意這個矩乘中還有一維要記錄字首和。

程式碼
#include <iostream>
#include <string.h>
#include <map>
#define int long long
using namespace std;
const int P = 1000000007;
const int N = 100;
struct Matrix {
    int a[105][105];
    int* operator[](int x) { return a[x]; }
    Matrix(int x = 0) {
        memset(a, 0, sizeof a);
        for (int i = 0; i <= N; i++) a[i][i] = x;
    }
} I, T;
Matrix operator*(Matrix a, Matrix b) {
    Matrix c;
    for (int i = 0; i <= N; i++) {
        for (int j = 0; j <= N; j++) {
            for (int k = 0; k <= N; k++) 
                c[i][j] = (c[i][j] + a[i][k] * b[k][j] % P) % P;
        }
    }
    return c;
}
Matrix operator^(Matrix x, int y) {
    Matrix ret(1);
    while (y) {
        if (y & 1) 
            ret = ret * x;
        y >>= 1;
        x = x * x;
    }
    return ret;
}
map<pair<int, int>, int> mp;
int f(int a, int b) { return mp[make_pair(a, b)]; }
int c[3], mxc, n;
signed main() {
    string a, b;
    cin >> a >> b;
    n = a.size();
    cin >> mxc;
    cin >> c[0] >> c[1] >> c[2] >> mxc;
    int Cost0 = 0, c1, c2;
    c1 = c2 = 0;
    for (int i = 0; i < n; i++) {
        if (a[i] == 'a' && b[i] == 'b') 
            Cost0 += c[0], ++c1;
        if (a[i] == 'a' && b[i] == 'c') 
            Cost0 += c[0] + c[1], ++c2;
        if (a[i] == 'b' && b[i] == 'a') 
            Cost0 += c[1] + c[2], ++c2;
        if (a[i] == 'b' && b[i] == 'c') 
            Cost0 += c[1], ++c1;
        if (a[i] == 'c' && b[i] == 'a') 
            Cost0 += c[2], ++c1;
        if (a[i] == 'c' && b[i] == 'b') 
            Cost0 += c[2] + c[0], ++c2;
    }
    int S = c[0] + c[1] + c[2];
    if (mxc < Cost0) {
        cout << 0 << "\n";
        return 0;
    }
    int MaxStep = (mxc - Cost0) / S;
    int ncnt = 0;
    for (int a1 = 0; a1 <= n; a1++) {
        for (int a2 = 0; a1 + a2 <= n; a2++) {
            mp[make_pair(a1, a2)] = ++ncnt;
        }
    }
    mp[make_pair(n, 1)] = ++ncnt;
    I[0][f(c1, c2)] = 1;
    for (int a1 = 0; a1 <= n; a1++) {
        for (int a2 = 0; a1 + a2 <= n; a2++) {
            int cur = f(a1, a2);
            if (a1) 
                T[cur][f(a1 - 1, a2)] = a1;
            if (a2) 
                T[cur][f(a1 + 1, a2 - 1)] = a2;
            if (a1 + a2 != n) 
                T[cur][f(a1, a2 + 1)] = n - a1 - a2;
        }
    }
    MaxStep = MaxStep * 3 + c1 + c2 * 2 + 1;
    T[f(0, 0)][f(n, 1)] = 1;
    T[f(n, 1)][f(n, 1)] = 1;
    I = (I * (T ^ MaxStep));
    cout << I[0][f(n, 1)] << "\n";
    return 0;
}

T5

Topcoder SRM 549 div1 Hard - CosmicBlocks

先列舉所有層各有哪些顏色,然後列舉相鄰層間所有顏色之間的覆蓋關係,然後由於要滿足這個覆蓋關係,我們從上往下建邊,由覆蓋的連向被覆蓋的,然後為了限制每個顏色的數量,我們對每個顏色拆點,拆出的點間邊容量為該顏色數量,所有邊有流量下界 \(1\),跑一個上下界網路流,若有解就再接一個 DAG 拓撲序計數,如果方案數合法就加入答案。

程式碼
#include <iostream>
#include <algorithm>
#include <time.h>
#include <random>
#include <queue>
using namespace std;
const int inf = 0x3f3f3f3f;
int n;
int LL, RR;
struct Edge {
    int u, v, l, r;
} e[1005];
int clr[10][10];
pair<int, int> es[15];
int a[10];
int in[105], out[105];
int head[1005], nxt[1005], to[1005], res[1005], ecnt;
int cur[1005];
inline void add(int u, int v, int ww) {
    to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt, res[ecnt] = ww;
    to[++ecnt] = u, nxt[ecnt] = head[v], head[v] = ecnt, res[ecnt] = 0;
}
int S, T;
int dep[1005];
queue<int> q;
bool bfs() {
    for (int i = 1; i <= T; i++) dep[i] = -1;
    dep[S] = 1;
    q.push(S);
    while (!q.empty()) {
        int x = q.front();
        q.pop();
        for (int i = head[x]; i; i = nxt[i]) {
            int v = to[i];
            if (dep[v] == -1 && res[i]) {
                dep[v] = dep[x] + 1;
                q.push(v);
            }
        }
    }
    return (dep[T] > 0);
}
int dfs(int x, int flow) {
    if (x == T) 
        return flow;
    int ret = 0;
    for (int& i = cur[x]; i; i = nxt[i]) {
        int v = to[i];
        if (dep[v] == dep[x] + 1 && res[i]) {
            int tmp = dfs(v, min(flow, res[i]));
            res[i] -= tmp;
            res[i ^ 1] += tmp;
            flow -= tmp;
            ret += tmp;
        }
    }
    if (!ret) 
        dep[x] = -1;
    return ret;
}
bool check(int m) {
    ecnt = 1;
    for (int i = 1; i <= T; i++) head[i] = 0, in[i] = out[i] = 0;
    for (int i = 1; i <= m; i++) {
        in[e[i].v] += e[i].l;
        out[e[i].u] += e[i].l;
        add(e[i].u, e[i].v, e[i].r - e[i].l);
    }
    S = n * 2 + 3, T = S + 1;
    for (int i = 1; i <= n * 2 + 1; i++) {
        if (in[i] < out[i]) 
            add(i, T, out[i] - in[i]);
        else if (in[i] > out[i]) 
            add(S, i, in[i] - out[i]);
    }
    while (bfs()) {
        for (int i = 1; i <= T; i++) cur[i] = head[i];
        dfs(S, inf);
    }
    bool flag = 1;
    for (int i = head[S]; i; i = nxt[i]) flag &= (res[i] == 0);
    return flag;
}
int dp[1005];
int count(int m, int S) {
    for (int i = 1; i <= n; i++) nxt[i] = 0;
    for (int i = 1; i <= m; i++) nxt[es[i].first] |= (((S >> (i - 1)) & 1) << (es[i].second - 1));
    for (int i = 1; i < (1 << n); i++) dp[i] = 0;
    for (int i = 1; i < (1 << n); i++) {
        for (int j = 1; j <= n; j++) {
            if (((i >> (j - 1)) & 1) & ((nxt[j] & i) == nxt[j])) 
                dp[i] += dp[i ^ (1 << (j - 1))];
        }
    }
    return dp[(1 << n) - 1];
}
int ans;
void get(int x, int S) {
    if (S == 0) {
        int e = 0;
        for (int i = 1; i < x - 1; i++) {
            for (int j = 1; j <= clr[i][0]; j++) {
                for (int k = 1; k <= clr[i + 1][0]; k++) 
                    es[++e] = make_pair(clr[i][j], clr[i + 1][k]);
            }
        }
        int c = 2 * n + 1;
        for (int i = 1; i <= clr[1][0]; i++) ::e[++c] = (Edge) { n << 1 | 1, clr[1][i] * 2 - 1, 0, inf };
        int rec = c;
        for (int i = 0; i < (1 << e); i++) {
            c = rec;
            for (int j = 0; j < e; j++) {
                if ((i >> j) & 1) 
                    ::e[++c] = (Edge) { es[j + 1].first * 2, es[j + 1].second * 2 - 1, 1, inf };
            }
            if (check(c)) {
                int cnt = count(e, i);
                ans += (LL <= cnt && cnt <= RR);
            }
        }
        return;
    }
    for (int i = S; i; i = (i - 1) & S) {
        clr[x][0] = 0;
        for (int j = 0; j < n; j++) {
            if ((i >> j) & 1) 
                clr[x][++clr[x][0]] = j + 1;
        }
        get(x + 1, S ^ i);
    }
}
int main() {
    *dp = 1;
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    sort(a + 1, a + n + 1);
    int t = clock();
    int c = 0;
    for (int i = 1; i <= n; i++) e[++c] = (Edge) { i << 1, n * 2 + 2, 0, inf }, e[++c] = (Edge) { i * 2 - 1, i << 1, a[i], a[i] };
    e[++c] = (Edge) { n * 2 + 2, n << 1 | 1, 0, inf };
    cin >> LL >> RR;
    get(1, (1 << n) - 1);
    cout << ans << "\n";
    cerr << (clock() * 1.0 - t) / CLOCKS_PER_SEC << "\n";
    return 0;
}

T6

Topcoder SRM 548 div1 Hard KingdomAndCities

先考慮 \(k = 0\) 的情況,即為 \(n\) 個點 \(m\) 條邊的無向連通圖計數。考慮容斥一下,數一數有多少不連通的。總共的方案數是 \(\binom{\binom{n}{2}}{m}\),列舉根所在的連通塊有幾個點、幾條邊,有轉移 \(f[n][m] = \sum_{i = 1}^{n - 1}\sum_{j = i - 1}^{\binom{i}{2}} f[i][j]\times \binom{\binom{n - i}{2}}{m - j}\),其中 \(f[i][j]\) 是根所在連通塊的方案數,後面是該連通塊之外隨便連的總方案數。這樣就解決了 \(k = 0\) 的情況。

考慮 \(k = 1\),相當於把一個點插到一條邊中間或者併到一條邊旁邊形成三元環。注意,如果形成非三元環的環,則該方案可能會被與插進邊的情況算重。兩種分別是多了一個點一條邊和一個點兩條邊,所以把 \(f\) 乘上各自選邊的方案數加起來即為答案。

考慮 \(k = 2\),首先考慮兩個插入的點之間有邊的情況。

  • 此時如果把兩個點共同連到一個點上形成三元環,則多了 \(2\) 個點 \(3\) 條邊,方案數即為原點數。此時是否交換兩個點的位置是一樣的。

  • 如果把兩個點共同插到一條邊裡去,則多了 \(2\) 個點 \(2\) 條邊,方案數為原邊數。此時交換兩個點的位置形成的方案不同,方案數要乘以 \(2\)

  • 如果把兩個點併到一條邊旁邊,則多了 \(2\) 個點 \(3\) 條邊,方案數為原邊數。此時交換兩個點形成的方案不同,方案數要乘以 \(2\)

考慮插入的兩個點之間沒有邊的情況。分別考慮兩個點以什麼形式插入。

  • 兩個點都形成三元環,多了 \(2\) 個點 \(4\) 條邊,選到相同邊不影響,方案數為原邊數的平方。

  • 一個點成三元環,一個點插入邊,此時兩個點不能選到相同邊,理由看下面的情況。相當於多了 \(2\) 個點 \(3\) 條邊,方案數為原邊數 乘以 原邊數減一。交換兩個點的方案不同,方案數要乘以 \(2\)

  • 兩個點都插入邊。

    • 插入相同邊,這種情況下有一個點的邊是原邊,一個點的邊要新開。這種情況實際上相當於前面那種情況選到了相同邊,但是這種情況下交換兩個點不影響,所以不能乘以 \(2\)。方案數為原邊數。
    • 插入不同邊,多了 \(2\) 個點 \(2\) 條邊,方案數為原邊數 乘以 原邊數減一。

將上面幾種的方案數全加起來即為答案。

注意特判一堆奇奇怪怪的邊界情況,記得開夠陣列。

程式碼
#include <iostream>
#define int long long
using namespace std;
const int P = 1000000007;
int n, m, k;
int f[3005][3005];
int C[3005][3005];
void Madd(int& x, int y) { (x += y) >= P ? (x -= P) : 0; }
signed main() {
    // freopen("data.in", "r", stdin);
    // freopen("data.out", "w", stdout);
    cin >> n >> k >> m;
    if (m < n - 1) {
        cout << 0 << "\n";
        return 0;
    }
    C[0][0] = 1;
    for (int i = 1; i <= 3000; i++) {
        for (int j = 0; j <= 3000; j++) 
            j == 0 ? (C[i][j] = 1) : (C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % P);
    }
    f[1][0] = 1;
    for (int i = 2; i <= n; i++) {
        for (int j = 0; j <= i * (i - 1) / 2; j++) {
            for (int a = 1; a < i; a++) {
                for (int b = a - 1; b <= j; b++) 
                    Madd(f[i][j], f[a][b] * C[i - 1][a - 1] % P * C[C[i - a][2]][j - b] % P);
            }
            f[i][j] = (C[C[i][2]][j] + P - f[i][j]) % P;
        }
    }
    if (n == 1 && m >= 1) {
        cout << "0\n";
        return 0;
    } else if (n == 1) {
        cout << (k == 0) << "\n";
        return 0;
    }
    if (n == 2 && m > 1) {
        cout << "0\n";
        return 0;
    } else if (n == 2) {
        cout << (k == 0) << "\n";
        return 0;
    }
    if (n == 3 && m > 3) {
        cout << "0\n";
        return 0;
    } else if (n == 3 && m == 3) {
        cout << "1\n";
        return 0;
    } else if (n == 3 && m == 2) {
        cout << (k == 0 ? 3 : (k == 1)) << "\n";
        return 0;
    } else if (n == 3) {
        cout << "0\n";
        return 0;
    }
    if (k == 0) 
        cout << f[n][m] << "\n";
    else if (k == 1) 
        cout << ((m - 2) * f[n - 1][m - 2] % P + (m - 1) * f[n - 1][m - 1] % P) % P << "\n";
    else {
        int a1 = ((n - 2) * f[n - 2][m - 3] % P + 2 * (m - 2) * f[n - 2][m - 2] % P + 2 * (m - 3) * f[n - 2][m - 3] % P) % P;
        int a2 = ((m - 4) * (m - 4) * f[n - 2][m - 4] % P + 2 * (m - 3) * (m - 4) * f[n - 2][m - 3] % P 
            + (m - 3) * f[n - 2][m - 3] % P + (m - 2) * (m - 3) * f[n - 2][m - 2] % P) % P;
        cout << (a1 + a2) % P << "\n";
    }
    return 0;
}