九月题目泛做总结。

Keywords

Problem On Tree, DFS & Optimization, Heap, Greedy, List, Interval&Backpack Dynamic Programming

Table of Contents

  1. Keywords
  2. BZOJ 2525 Dynamite
    1. Description
    2. Solution
  • BZOJ 1306 循环赛
    1. Description
    2. Solution
  • BZOJ 2151 种树, BZOJ 1150 数据备份
    1. Description
    2. Solution
  • BZOJ 1296 粉刷匠
    1. Description
    2. Solution
  • BZOJ 2525 Dynamite

    Description

    一颗 $n$ 个点的树, 有一些关键点, 需要选择关键点, 最小化选点到所有关键点距离的最大值.

    Solution

    直接做肯定是不好做, 我们转验证.

    那么怎么验证距离是否小于等于 $mid$ 呢? 一开始以为要再套个DP, 其实是贪心, 需要记录两种信息, 来确定是子树内部可以解决掉所有关键点, 还是需要外面的帮助.也就是树内已选点到根的最小距离 $d_1$ 和树内未被覆盖的关键点的最大深度 $d_2$.

    如果两者相加小于 $mid$, 说明可以自己deal. 直接把 $d_1$置0, $d_2$置 $-\infty$.

    如果 $d_2 = mid$, 树根强制选, 记录下答案.

    需要注意的是 $d_1 > mid$ 的情况, 这时候要先修正一下 $d_2$. 就算子树中的炸弹都搞定了, 自己也有可能是炸弹, $d_2 = max(d_2, 0)$.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    #include <iostream>
    #include <algorithm>
    #include <cstring>
    #include <cstdio>
    #include <cctype>

    using namespace std;

    const int maxn = 300005;
    const int inf = 0x3f3f3f3f;

    struct edge
    {
    int to, nxt;
    }e[maxn << 1];
    int n, m, ptr, lnk[maxn], d[maxn], cnt;
    int f[maxn], g[maxn], tot;

    inline void add(int bgn, int end)
    {
    e[++ptr].to = end; e[ptr].nxt = lnk[bgn];
    lnk[bgn] = ptr;
    }
    void dfs(int x, int fa, int mid)
    {
    f[x] = -inf; g[x] = inf;
    for(int p = lnk[x]; p; p = e[p].nxt)
    {
    int y = e[p].to;
    if(y == fa) continue;
    dfs(y, x, mid);
    f[x] = max(f[x], f[y] + 1);
    g[x] = min(g[x], g[y] + 1);
    }
    if(d[x] && g[x] > mid) f[x] = max(f[x], 0); //either self or subtree
    if(f[x] + g[x] <= mid) f[x] = -inf; //do it self no warry
    if(f[x] == mid) ++tot, f[x] = -inf, g[x] = 0; //there's no bomb anymore, and this point is chosen.
    }
    inline bool chk(int mid)
    {
    tot = 0;
    dfs(1, 0, mid);
    if(f[1] >= 0) tot++;
    return tot <= m;
    }
    inline int rd()
    {
    register int x = 0; char c = getchar();
    while(!isdigit(c)) c = getchar();
    while(isdigit(c)) x = x * 10 + (c ^ 48), c = getchar();
    return x;
    }

    int main()
    {
    scanf("%d%d", &n, &m);
    for(register int i = 1; i <= n; ++i) d[i] = (rd() == 1);
    int x, y;
    for(register int i = 1; i < n; ++i)
    {
    x = rd(); y = rd();
    add(x, y); add(y, x);
    }
    int l = 0, r = n, ans = 0;
    while(l <= r)
    {
    int mid = (l + r) >> 1;
    if(chk(mid)) ans = mid, r = mid - 1;
    else l = mid + 1;
    }
    printf("%d\n", ans);
    return 0;
    }

    BZOJ 1306 循环赛

    Description

    若干支队伍互相比赛, 胜+3, 负0, 平各+1.

    给出比分情况, 求可能局面方案数.

    $n \le 8$.

    Solution

    一看这个数据范围就比较适合乱搞.

    剪枝三连: 全胜无解, 全负无解, 当前队伍最后一场比赛如果分差为2无解.

    加强版就没这么简单了, 有机会做一下.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    #pragma GCC optimize(3)
    #include <cstdio>

    int n, scr[10], ans, tot;
    int f[4] = {3, 1, -1, 0};
    int tmp[10];

    inline void dfs(int x, int y)
    {
    if(tmp[x] + (n - y + 1) * 3 < scr[x]) return;
    if(tmp[x] > scr[x]) return;
    if(x == n) {ans++; return;}
    if(y == n)
    {
    int delt = scr[x] - tmp[x];
    if(delt == 2) return;
    tmp[y] += f[delt];
    dfs(x + 1, x + 2);
    tmp[y] -= f[delt];
    }
    else
    {
    tmp[x] += 3; dfs(x, y + 1); tmp[x] -= 3;
    tmp[y] += 3; dfs(x, y + 1); tmp[y] -= 3;
    tmp[x]++; tmp[y]++; dfs(x, y + 1); tmp[x]--; tmp[y]--;
    }
    }

    int main()
    {
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i) scanf("%d", &scr[i]);
    dfs(1, 2);
    printf("%d\n", ans);
    return 0;
    }

    BZOJ 2151 种树, BZOJ 1150 数据备份

    Description

    给定每个位置的权, 不能选择相邻位置, 最大化权值和.

    Solution

    考虑选择一个的代价, 来修改相邻元素, 以便在更优答案时修正.

    这是堆的题的一个典型性质.

    这道题中, 将相邻的都删除, 变成一个大点, 点权是 $l + r - v$.

    洛谷上还有个上环的, 也同理.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    #include <iostream>
    #include <algorithm>
    #include <cstring>
    #include <cstdio>
    #include <queue>
    #include <vector>
    #include <cctype>

    using namespace std;

    const int maxn = 200005;

    struct node
    {
    int val, id;
    node(int v_ = 0, int i_ = 0):
    val(v_), id(i_){}
    bool operator < (const node &rhs) const
    {
    return val < rhs.val;
    }
    };
    int ptr, cnt, ans, lst[maxn], nxt[maxn];
    int n, k, d[maxn], tag[maxn];
    priority_queue<node> q;

    inline int rd()
    {
    register int x = 0, f = 0; char c = getchar();
    while(!isdigit(c)){if(c == '-') f = 1; c = getchar();}
    while(isdigit(c)) x = x * 10 + (c ^ 48), c = getchar();
    return f ? -x : x;
    }
    inline void remove(int x)
    {
    tag[x] = 1;
    lst[nxt[x]] = lst[x];
    nxt[lst[x]] = nxt[x];
    }

    int main()
    {
    n = rd(); k = rd();
    if(k > n / 2)
    {
    puts("Error!");
    return 0;
    }
    for(int i = 1; i <= n; ++i) d[i] = rd();
    lst[1] = n; nxt[n] = 1;
    for(int i = 2; i <= n; ++i) nxt[i-1] = i, lst[i] = i - 1;
    for(int i = 1; i <= n; ++i) q.push(node(d[i], i));
    cnt = k;
    while(cnt--)
    {
    //node u = q.top(); q.pop();
    while(tag[q.top().id]) q.pop();
    node u = q.top(); q.pop();
    ans += u.val;
    //printf("%d %d\n", u.id, ans);
    int va = d[lst[u.id]], vb = d[nxt[u.id]];
    remove(lst[u.id]); remove(nxt[u.id]);
    d[u.id] = va + vb - u.val;
    q.push(node(d[u.id], u.id));
    }
    printf("%d\n", ans);
    return 0;
    }

    BZOJ 1296 粉刷匠

    Description

    有若干条木板, 每一格只能刷一种给定的颜色, 不刷或刷错都是错. 颜色只有两种.

    刷一次只能在一条木板上刷连续区间, 给定刷 $T$ 次, 问最多刷正确多少块.

    Solution

    一眼区间DP套背包, 遂上状态 $f[i][j][k]$, 肯定都能明白.

    发现跪了. 那$f[i][j][k][0/1]$. 还是很明白.

    怎么又跪了的说!!! $f[i][j][k][0/1][0/1]$!

    算了算了转移太长了.

    其实是在一条上做 $n^2$, 每次枚举断点, 前缀和优化一下.

    然后再背包.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    #include <iostream>
    #include <algorithm>
    #include <cstring>
    #include <cstdio>

    using namespace std;

    int n, m, t;
    int c[55][55], sum[55];
    char board[55][55];
    int dp[55][2505];

    int main()
    {
    scanf("%d%d%d", &n, &m, &t);
    for(int i = 1; i <= n; ++i)
    {
    scanf("%s", board[i] + 1);
    for(int j = 1; j <= m; ++j)
    sum[j] = sum[j - 1] + (board[i][j] ^ 48);
    for(int T = 1; T <= m; ++T)
    for(int j = 1; j <= m; ++j)
    {
    c[j][T] = 0;
    for(int k = 0; k < j; ++k)
    {
    int cnt = sum[j] - sum[k];
    c[j][T] = max(c[j][T], c[k][T - 1] + max(cnt, j - k - cnt));
    }
    }
    for(int j = 1; j <= t; ++j)
    for(int k = 1; k <= min(m, j); ++k)
    dp[i][j] = max(dp[i][j], dp[i - 1][j - k] + c[m][k]);
    }
    int ans = 0;
    for(int i = 1; i <= t; ++i)
    ans = max(ans, dp[n][i]);
    printf("%d\n", ans);
    return 0;
    }