四月 杂题题解

最小步数

题意

\(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\times 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);
}
posted @ 2022-04-15 18:11  小蒟蒻laf  阅读(50)  评论(0)    收藏  举报