striving & singing

2020-09-11

ZhengruIOI 普及组五连测 Day1。

A. 【20zr普及组五连测day1】排列

http://www.zhengruioi.com/contest/695/problem/1545

看到题目直接打表,然后发现答案是一个斐波那契数列,然后直接矩阵快速幂即可。

原理是设$f(i)$表示$i$的答案,那么第$i$个位置只能填$i$或$i-1$,填$i$时剩余位置的方案数为$f(i-1)$,填$i-1$时第$i-1$个位置只能填$i$,那么剩余位置的方案数为$f(i-2)$。因此$f(i)=f(i-1)+f(i-2)$。

懂了,矩阵快速幂是普及组 T1 难度考点。

#include<cstdio>
#include<cstring>

const int MOD = 998244353;
long long n;
struct matrix {
    int l, w, a[3][3];
    matrix(int lv = 0, int wv = 0): l(lv), w(wv) { memset(a, 0, sizeof a); }
    int* operator [] (const int &idx) { return &a[idx][0]; }
    matrix operator * (matrix b) {
        matrix ans(l, b.w);
        for (int i = 1; i <= ans.l; i++)
            for (int j = 1; j <= ans.w; j++)
                for (int k = 1; k <= w; k++) ans[i][j] = (ans[i][j] + (long long)a[i][k] * b[k][j] % MOD) % MOD;
        return ans;
    }
    inline void build() { for (int i = 1; i <= l; i++) a[i][i] = 1; }
}f(2, 1), trs(2, 2);

inline matrix pwr(matrix x, long long k) {
    matrix ans(x.l, x.w); ans.build();
    for (; k; x = x * x, k >>= 1)
        if (k & 1) ans = ans * x;
    return ans;
}

int main() {
    f[1][1] = 1;
    trs[1][1] = trs[1][2] = trs[2][1] = 1;
    scanf("%lld", &n);
    f = pwr(trs, n) * f;
    printf("%d\n", f[1][1]);
    return 0;
}

B. 【20zr普及组五连测day1】游走

http://www.zhengruioi.com/contest/695/problem/1546

设一条路径经过了$k$个点,最后在深度为$d$的节点停下,那么最小步数为$2(k-1)-d$。可以考虑在走完某条路径后再回到根节点,那么最小步数为$2(k-1)$,因为显然每条边都最少经过两次。然后因为不用回到根节点所以最小步数为$2(k-1)-d$。然后根据题目条件有$2(k-1)-\min(m,d)\le m$,这里$d$变成了$\min(m,d)$的原因是当$d>m$时无法走到深度为$d$的点,最多只能走到深度为$m$的点。因此$k\le\dfrac{m+\min(m,d)}{2}+1$,所以答案就是$\min(n,\dfrac{m+\min(m,d)}{2}+1)$。

#include<cstdio>

int n, m, d;
struct edge { int to, next; };
struct graph {
    int ecnt = 1, head[100100];
    edge edges[200100];
    inline void addedge(int u, int v) {
        edges[++ecnt].to = v;
        edges[ecnt].next = head[u];
        head[u] = ecnt;
    }
}g;

inline int min(int a, int b) { return a < b ? a : b; }
inline int max(int a, int b) { return a > b ? a : b; }
void dfs(int x, int lst, int cnt) {
    d = max(d, cnt);
    for (int i = g.head[x]; i; i = g.edges[i].next) {
        if ((i ^ lst) == 1) continue;
        int &v = g.edges[i].to;
        dfs(v, i, cnt + 1);
    }
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i < n; i++) {
        int u, v; scanf("%d%d", &u, &v);
        g.addedge(u, v); g.addedge(v, u);
    }
    dfs(1, 0, 0);
    printf("%d\n", min(n, (m + min(m, d)) / 2 + 1));
    return 0;
}

C. 【20zr普及组五连测day1】颜色

http://www.zhengruioi.com/contest/695/problem/1547

可以考虑每种颜色对答案的贡献即包含这种颜色的路径条数,那么补集转化一下就是所有路径减去不包含这种颜色的路径条数。所有这种颜色的点把树分成若干连通块,每个连通块的路径条数就是$\binom{size}{2}$,除根节点所在的连通块外,每个连通块都有一个位于顶端的这种颜色的节点,可以在这个节点处计算连通块大小。

有一个巧妙的方法:设$s_i$为以节点$i$为顶端的连通块大小,$f[c[x]]$表示当前所有以第$c[x]$种颜色的节点为顶端的连通块大小$+1$之和,那么在递归遍历完以子节点$v$为根的子树后,可以得到$f[c[x]]$的增量,而$size[v]$减去这个增量就是$v$所在的连通块的大小。之所以要使$f[c[x]]$加一,是因为还要算上这个颜色的节点,这样用$size[v]$减才是对的。

#include<cstdio>

int n, c[200100], size[200100], f[200100], cn;
bool vis[200100];
long long ans;
struct edge { int to, next; };
struct graph {
    int ecnt = 1, head[200100];
    edge edges[400100];
    inline void addedge(int u, int v) {
        edges[++ecnt].to = v;
        edges[ecnt].next = head[u];
        head[u] = ecnt;
    }
}g;

void dfs(int x, int lst) {
    size[x] = 1; f[c[x]]++;
    for (int i = g.head[x]; i; i = g.edges[i].next) {
        if ((i ^ lst) == 1) continue;
        int &v = g.edges[i].to, s = f[c[x]];
        dfs(v, i);
        size[x] += size[v];
        s = size[v] - (f[c[x]] - s);
        f[c[x]] += s;
        ans -= (long long)s * (s - 1) / 2;
    }
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d", &c[i]), vis[c[i]] = true;
    for (int i = 1; i <= n; i++) if (vis[i]) cn++;
    for (int i = 1; i < n; i++) {
        int u, v; scanf("%d%d", &u, &v);
        g.addedge(u, v); g.addedge(v, u);
    }
    ans = (long long)cn * n * (n - 1) / 2;
    dfs(1, 0);
    for (int i = 1; i <= n; i++) if (vis[i]) ans -= (long long)(n - f[i]) * (n - f[i] - 1) / 2;
    printf("%lld\n", ans);
    return 0;
}

D. 【20zr普及组五连测day1】区间

http://www.zhengruioi.com/contest/695/problem/1548

是个 DP,然后题解其他部分我就看不懂了,垃圾题解写了跟没写一样,连转移方程都没有,状态含义也是含糊不清。等我 DP 水平提高点再来补。

2020-09-13

宝物筛选

https://www.luogu.com.cn/problem/P1776

多重背包模板题。设$dp[j]$表示所选重量之和为$j$时的最大价值和,那么$dp[j]=\max\limits_{i=1}^c (dp[j-wi]+vi)$,可以用把$j$按照$j\bmod w$的值分组,然后直接对每组 DP,从而可以用单调队列优化。不用推式子,但其他题解上都推了一堆式子,不是很懂。

#include<cstdio>
#include<queue>

int n, m;
long long dp[40100], ans;
std::deque<int> q;

inline long long max(long long a, long long b) { return a > b ? a : b; }

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) {
        int v, w, c; scanf("%d%d%d", &v, &w, &c);
        for (int b = 0; b < w; b++) {
            int cur = (m - b) / w - 1;
            q.clear();
            for (int a = (m - b) / w; a >= 0; a--) {
                while (!q.empty() && q.front() >= a) q.pop_front();
                while (cur >= 0 && cur >= a - c) {
                    while (!q.empty() && dp[q.back() * w + b] + v * (a - q.back()) <= dp[cur * w + b] + v * (a - cur)) q.pop_back();
                    q.push_back(cur);
                    cur--;
                }
                if (!q.empty()) dp[a * w + b] = max(dp[a * w + b], dp[q.front() * w + b] + v * (a - q.front()));
            }
        }
    }
    for (int i = 1; i <= m; i++) ans = max(ans, dp[i]);
    printf("%lld\n", ans);
    return 0;
}

樱花

https://www.luogu.com.cn/problem/P1833

混合背包模板题,直接写即可。

#include<cstdio>
#include<queue>

int n, m, tshh, tsmm, tehh, temm;
long long dp[1010], ans;
std::deque<int> q;

inline long long max(long long a, long long b) { return a > b ? a : b; }

int main() {
    scanf("%d:%d %d:%d %d", &tshh, &tsmm, &tehh, &temm, &n);
    m = tehh * 60 + temm - (tshh * 60 + tsmm);
    for (int i = 1; i <= n; i++) {
        int w, v, c; scanf("%d%d%d", &w, &v, &c);
        if (c == 0) for (int j = w; j <= m; j++) dp[j] = max(dp[j], dp[j - w] + v);
        else if (c == 1) for (int j = m; j >= w; j--) dp[j] = max(dp[j], dp[j - w] + v);
        else {
            for (int b = 0; b < w; b++) {
                int cur = (m - b) / w - 1;
                for (int a = (m - b) / w; a >= 0; a--) {
                    while (!q.empty() && q.front() >= a) q.pop_front();
                    while (cur >= 0 && cur >= a - c) {
                        while (!q.empty() && dp[q.back() * w + b] + v * (a - q.back()) <= dp[cur * w + b] + v * (a - cur)) q.pop_back();
                        q.push_back(cur);
                        cur--;
                    }
                    if (!q.empty()) dp[a * w + b] = max(dp[a * w + b], dp[q.front() * w + b] + v * (a - q.front()));
                }
            }
        }
    }
    for (int i = 1; i <= m; i++) ans = max(ans, dp[i]);
    printf("%lld\n", ans);
    return 0;
}
return