1. 程式人生 > 其它 >四月 雜題題解

四月 雜題題解

最小步數

題意

\(n+1\)\(m\) 列的表格,從最第一行任意點開始,每次向右或向下走一格

對於 \(1\le i\le n\) 有區間 \([A_i,B_i]\) 在這些各自上時,不能向下移動。

對於 \(2\le K\le H+1\) ,求出從第一行到第 \(K\) 行的最少步數

sol

線段樹,若對於區間 \([L,R]\) 不能向下走,則

  • 區間 \([1,L-1],[R+1,m]\) 全部加 1

  • \(i\in[L,R]\) 改為 \(dis(L-1)+i-L+1\) 因為必須從 \(L-1\) 走來

    具體的:記錄當前區間左端點的值,以及是否需要下傳,每次懶標記就更新這個值

  • 求最小值

兩個修改一個查詢

code

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
const int INF = 1e8;
int n, m, a[N], b[N], R, st, fl[N << 2], ad[N << 2];
int mn[N << 2], le[N << 2], ans[N];
struct Seg {
    int l, r;
} T[N << 2];
#define ls (rt << 1)
#define rs (rt << 1 | 1)
inline void Up(int rt) {
    mn[rt] = min(mn[ls], mn[rs]);
    le[rt] = le[ls];
}
inline void down(int rt) {
    if (fl[rt]) {
        register int L = le[rt];
        fl[ls] = 1, ad[ls] = 0, mn[ls] = le[ls] = L;
        fl[rs] = 1, ad[rs] = 0, mn[rs] = le[rs] = L + T[rs].l - T[rt].l;
    } else if (ad[rt]) {
        ad[ls] += ad[rt], mn[ls] += ad[rt], le[ls] += ad[rt];
        ad[rs] += ad[rt], mn[rs] += ad[rt], le[rs] += ad[rt];
    }
    ad[rt] = fl[rt] = 0;
}
void bui(int l, int r, int rt) {
    T[rt].l = l, T[rt].r = r;
    if (l == r)
        return;
    register int mid = l + r >> 1;
    bui(l, mid, ls), bui(mid + 1, r, rs);
}
void add(int ql, int qr, int rt) {
    if (ql <= T[rt].l && T[rt].r <= qr) {
        ++mn[rt], ++ad[rt], ++le[rt];
        return;
    }
    down(rt);
    if (ql <= T[ls].r)
        add(ql, qr, ls);
    if (qr >= T[rs].l)
        add(ql, qr, rs);
    Up(rt);
}
void upd(int ql, int qr, int L, int rt) {
    if (ql <= T[rt].l && T[rt].r <= qr) {
        le[rt] = mn[rt] = L + T[rt].l - ql + 1;
        fl[rt] = 1;
        return;
    }
    down(rt);
    if (ql <= T[ls].r)
        upd(ql, qr, L, ls);
    if (qr >= T[rs].l)
        upd(ql, qr, L, rs);
    Up(rt);
}
int ask(int p, int rt) {
    if (T[rt].l == T[rt].r)
        return mn[rt];
    down(rt);
    return p <= T[ls].r ? ask(p, ls) : ask(p, rs);
}
#undef ls
#undef rs
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) scanf("%d%d", &a[i], &b[i]);
    bui(1, m, 1);
    for (int i = 1; i <= n; i++) ans[i] = INF;
    for (int i = 1, L; i <= n; i++) {
        if (a[i] > 1)
            add(1, a[i] - 1, 1);
        if (b[i] < m)
            add(b[i] + 1, m, 1);
        if (a[i] > 1)
            L = ask(a[i] - 1, 1);
        else
            L = INF;
        upd(a[i], b[i], L, 1);
        ans[i] = min(ans[i], mn[1]);
        if (ans[i] == INF)
            break;
    }
    for (int i = 1; i <= n; i++) printf("%d\n", ans[i] < INF ? ans[i] : -1);
}

彩色蠟筆

題意

\(n\) 只由 \(R_i,G_i,B_i\) 表示的彩色蠟筆,

\(i,j\) 兩隻蠟筆的色彩差異度為 \(\max(|R_i-R_j|,|G_i-G_j|,|B_i-B_j|)\)

選出 \(K\) 只蠟筆組成的子序列(不用連續),使差異度最小

\(R_i,G_i,B_i\le255,n\le 10^5\)

sol

三維字首和,值域小,可將問題轉為判斷性問題。

求三維空間內的點是否 \(\ge K\)

  • 二分答案,列舉 \(R,G,B\) 上界,\(O(1)\) 判斷,總 \(O(255^3\log 255)\)
  • 對一維,如 \(R\) 使用雙指標,列舉 \(G,B\)
    上界,總 \(O(255^3)\)

code

#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long uLL;
typedef long double LD;
typedef long long LL;
typedef double db;
const int M = 260;
int n, K, s[M][M][M], ans = 1e9, L, R;
inline bool chk() {
    register int x = R - L + 1;
    for (int i = x; i <= 256; i++)
        for (int j = x; j <= 256; j++)
            if (s[R][i][j] - s[L - 1][i][j] - s[R][i - x][j] - s[R][i][j - x] + s[L - 1][i - x][j] +
                    s[L - 1][i][j - x] + s[R][i - x][j - x] - s[L - 1][i - x][j - x] >=
                K)
                return 1;
    return 0;
}
int main() {
    scanf("%d%d", &n, &K);
    for (int i = 1, x, y, z; i <= n; i++) scanf("%d%d%d", &x, &y, &z), s[x + 1][y + 1][z + 1]++;
    for (int i = 1; i <= 256; i++)
        for (int j = 1; j <= 256; j++)
            for (int k = 1; k <= 256; k++)
                s[i][j][k] += s[i - 1][j][k] + s[i][j - 1][k] + s[i][j][k - 1] - s[i - 1][j - 1][k] -
                              s[i - 1][j][k - 1] - s[i][j - 1][k - 1] + s[i - 1][j - 1][k - 1];
    for (L = 1, R = 1; R <= 256; R = max(R, ++L)) {
        while (R <= 256 && !chk()) R++;
        if (R <= 256)
            ans = min(ans, R - L);
    }
    printf("%d", ans);
}

圖的數量

題意

\(n\) 個點, \(m\) 條未標號邊,且滿足下列條件:

  • 沒有自環
  • 每個點度最大為 2
  • 最大連通塊剛好 \(L\) 個點

求圖的數量, \(\mod 10^9+7\)

sol

  1. 度不超過 2,即不是環就是鏈

  2. 最大連通塊數量恰好為 \(K\) ,不好求,考慮字首和做差

    \(F_k\) 為最大連通塊大小不超過 \(k\) 的數量,則答案為 \(F_k-F_{k-1}\)

\(f_{i,j}\) 為用了 \(i\) 個點, \(j\) 條邊的滿足條件的圖的數量

  • \(f_{i,j}\rightarrow f_{i+1,j}\) ,將 \(i\) 單獨做連通塊

  • 若點 \(i\) 在一條長度為 \(k(2\le k\le L)\) 的鏈上,

    \(f_{i,j}\times C_{n-i-1}^{k-1}\times \dfrac{k!}{2}\rightarrow f_{i+k,j+k-1}\)

    即該點必選,再從 \(n-i-1\) 中選 \(k-1\) 個點,選出的點組成 \(\dfrac{k!}{2}\) 條鏈

  • \(i\) 在長度為 2 的鏈上,則 \(f_{i,j}\times(n-i-1)\rightarrow f_{i+2,j+2}\)

  • 該點在長度為 \(k(3\le k\le L)\) ,則

    \(f_{i,j}\cdot C_{n-i-1}^{k-1}\cdot\frac{(k-1)!}{2}\rightarrow f_{i+k,j+k}\)

即可求出 \(F_k\) ,同理求出 \(F_{k-1}\)

記得逆元

code

#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long uLL;
typedef long double LD;
typedef long long LL;
typedef double db;
const LL P = 1e9 + 7;
const LL inv2 = 500000004;
int n, m, MX;
LL C[1005][1005], f[305][305], fac[1005], fR, fL;
inline int get(int L) {
    if (L == 1)
        return 0;
    memset(f, 0, sizeof(f));
    f[0][0] = 1;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j <= m; j++) {
            (f[i + 1][j] += f[i][j]) %= P;
            if (i + 2 <= n && j + 2 <= m)
                (f[i + 2][j + 2] += f[i][j] * (n - i - 1) % P) %= P;
            for (int k = 2; k <= L && i + k <= n; k++) {
                if (j + k - 1 <= m)
                    (f[i + k][j + k - 1] += f[i][j] * C[n - i - 1][k - 1] % P * fac[k] % P * inv2 % P) %= P;
                if (k > 2 && j + k <= m)
                    (f[i + k][j + k] += f[i][j] * C[n - i - 1][k - 1] % P * fac[k - 1] % P * inv2 % P) %= P;
            }
        }
    }
    return f[n][m];
}
int main() {
    C[0][0] = fac[0] = 1;
    for (int i = 1; i <= 1000; i++) {
        C[i][0] = 1, fac[i] = fac[i - 1] * i % P;
        for (int j = 1; j <= i; j++) C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % P;
    }
    scanf("%d%d%d", &n, &m, &MX);
    if (MX == 1)
        return puts("0"), 0;
    // cout << get(MX) << ' ' << get(MX - 1) << endl;
    printf("%lld", (get(MX) - get(MX - 1) + P) % P);
}

糾結的數

題意

求第 \(n\) 小的正整數 \(X\) ,滿足 \(X\) 的最小素因子為 \(P\) ,若 \(X\ge 10^9\) ,輸出 0

sol

  • \(n=1\) 則直接輸出 \(p\) ,否則答案最小為 \(p^2\) ,若不為 0 則 \(p\le \sqrt{10^9}\) ,範圍縮小

運用到了拼盤的思路

  1. \(p\ge100\) 可以用小於 \(P\) 的質數暴力標記 \(P\) 的倍數,

    複雜度為 \(\dfrac{10^9}{2p}+\dfrac{10^9}{3p}+\dfrac{10^9}{5p}\cdots\) ,略小於 \(\ln \dfrac{10^9}{p}\)

  2. \(p< 100\) 時,暴力的複雜度不可取,但是 100 以內只有 25 個質數

    可以二分 \(p\) 的倍數 \(X\) ,求出能被小於 \(p\) 的素數整除的數,可以容斥解決

    複雜度 \(O(2^{25}\log 10^9)\)

code

#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long uLL;
typedef long double LD;
typedef long long LL;
typedef double db;
const int INF = 1e9;
const int SQ = 31623;
int n, P, mx, ans, tmp, mid;
bitset<20000000> mark;
int pr[SQ + 5], cnt, vis[SQ + 5];
void dfs(int i, int s, int op) {
    if (pr[i] == P) {
        tmp += op * mid / s;
        return;
    }
    dfs(i + 1, s, op);
    if (s <= mid / pr[i])
        dfs(i + 1, s * pr[i], -op);
}
int main() {
    for (int i = 2; i <= SQ; i++) {
        if (!vis[i])
            pr[++cnt] = i;
        for (int j = 1; j <= cnt && i * pr[j] <= SQ; j++) {
            vis[i * pr[j]] = 1;
            if (i % pr[j] == 0)
                break;
        }
    }
    scanf("%d%d", &n, &P);
    if (n == 1)
        return printf("%d", P), 0;
    mx = INF / P;
    if (P > mx)
        return puts("0"), 0;
    if (P >= 100) {
        for (int i = 1; i <= cnt && pr[i] < P; i++)
            for (int j = pr[i]; j <= mx; j += pr[i]) mark[j] = 1;
        for (int i = 1, tc = 0; i <= mx; i++) {
            if (!mark[i])
                ++tc;
            if (tc == n) {
                ans = i * P;
                break;
            }
        }
        printf("%d", ans);
    } else {
        int l = 2, r = mx + 1, res;
        while (l <= r) {
            mid = l + r >> 1;
            tmp = 0, dfs(1, 1, 1);
            if (tmp >= n)
                r = mid - 1, res = mid;
            else
                l = mid + 1;
        }
        printf("%d", res > mx ? 0 : res * P);
    }
}

刪牌遊戲

題意

\(3\times n\) 張牌,牌上數字為 \(A_i\) ,刪 \(n-1\) 次牌

每次從左邊 5 張任意刪去 3 張,若這 3 張牌數字相同可以得 1 分

最後 3 張牌數字相同可以再得 1 分,求最大得分

\(n\le 2000\)

sol

\(dp_{i,x,y}\) 為第 \(i\) 輪時之前剩下 \(x,y\) 的最大得分,此時若三、四、五張牌為 \(A,B,C\)

\(f_{i+1,x',y'}=\max f_{i,x,y}+eq\) ,其中 \(eq\) 表示剩下 3 張是否相等, \(x',y'\) 表示在 \(x,y,A,B,C\) 中任選 2 張

一次轉移 \(C_5^2\) ,總 \(O(C_5^2 n^3)\) ,光榮 TLE


可以壓掉 \(i\) ,設狀態為 \(f_{x,y}\) 表示最左邊剩下 \(x,y\) 時的最大得分

狀態無法壓縮,需要分類討論,減少列舉的狀態數

  1. \(x',y'\) 就是 \(x,y\) ,若 \(A=B=C\) ,答案整體加 1,可用一個變數 \(plus\) 記錄

  2. \(x',y'\) 有一個是 \(A,B,C\) 中的值,假設 \(x'=x,y'=A\) (最多 6 種情況)

    \(f_{x',y'}=f_{x',A}=\max(f_{x',y}+eq)\) ,只需列舉 \(y\) 。預處理 \(\max dp_{x',y}\) ,複雜度 \(O(n)\)

  3. \(x',y'\) 都是 \(A,B,C\) 中的值,假設 \(x'=A,y'=B\) (最多 3 種情況)

    \(f_{x',y'}=f_{A,B}=\max (f_{C,C}+1,f_{x,y})\),複雜度為 \(O(1)\)

  4. 可用一個數組 \(g\) 輔助更新,每次 \(g_{x,y}\) 改變就存下 \((x,y)\) 之後用於改 \(f_{x,y}\)

    因為 \(g\) 每次改動最多 \(3\cp 3n\) 次,這樣可以避免列舉,使複雜度降到 \(O(n^2)\)

答案直接列舉最後兩個數,最後加上 \(plus\) 即可

總共 \(O(n^2)\)

code

#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long uLL;
typedef long double LD;
typedef long long LL;
typedef double db;
const int N = 2005;
int n, vl[N * 3], pls, f[N][N], g[N][N], mx[N], tx[N], tt, le, xx[N * 9], yy[N * 9], ans = -1e9;
inline void px(int &A, int &B, int &C) {
    if (A > B) A ^= B ^= A ^= B;
    if (A > C) A ^= C ^= A ^= C;
    if (B > C) B ^= C ^= B ^= C;
}
inline void push(int x, int y) {
    if (x > y) x ^= y ^= x ^= y;
    tx[x] = max(tx[x], g[x][y]);
    tx[y] = max(tx[y], g[x][y]);
    xx[++le] = x, yy[le] = y;
}
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= 3 * n; i++) scanf("%d", &vl[i]);
    for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) f[i][j] = g[i][j] = -1e9;
    f[vl[1]][vl[2]] = f[vl[2]][vl[1]] = 0;
    mx[vl[1]] = mx[vl[2]] = 0;
    for (int o = 1, a, b, c; o < n; o++) {
        a = vl[3 * o], b = vl[3 * o + 1], c = vl[3 * o + 2];
        le = 0;
        if (a == b && b == c) { ++pls; continue; }
        px(a, b, c);
        for (int i = 1; i <= n; i++) tx[i] = mx[i];
        for (int i = 1, x; i < 3 * o; i++) {
            x = vl[i];
            g[x][a] = g[a][x] = max(mx[x], f[x][b] + (b == c)), push(x, a);
            g[x][b] = g[b][x] = max(mx[x], f[x][c] + (a == c)), push(x, b);
            g[x][c] = g[c][x] = max(mx[x], f[x][a] + (a == b)), push(x, c);
        }
        tt = -1e9;
        for (int i = 1; i <= n; i++) tt = max(tt, mx[i]);
        g[a][b] = max(tt, f[c][c] + 1), push(a, b);
        g[a][c] = max(tt, f[b][b] + 1), push(a, c);
        g[b][c] = max(tt, f[a][a] + 1), push(b, c);
        for (int i = 1; i <= n; i++) mx[i] = tx[i];
        for (int i = 1, x, y; i <= le; i++) {
            x = xx[i], y = yy[i];
            f[x][y] = f[y][x] = max(f[x][y], g[x][y]);
        }
    }
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++) ans = max(ans, f[i][j] + (i == j && j == vl[3 * n]));
    printf("%d", ans + pls);
}