「2019纪中集训Day20」解题报告

T1、Global warming

题目链接

给定整数 \(n \ (n \leq 2 \times 10 ^ 5)\)\(x \ (x \leq 10 ^ 9)\),以及一个长度为 \(n\) 的序列 \(a \ (a_i \leq 10 ^ 9)\)
你可以选择一个区间 \([l,r]\),然后令 \(a_i = a_i + d \ (i \in [l,r] \bigcap \Z)\),其中 \(d\) 满足 \(|d|<=x\)
要求最大化 \(a\) 的最长上升子序列的长度,并输出该值。

\(Sol\)

有一个比较明显的性质:
\([l, r]\)\(d\) 不比 给 \([l, n]\)\(d\) 优;
\([l, r]\)\(d\) 不比 给 \([1, r]\)\(d\) 优。
而且给一个前缀减 \(d\) 和给一个后缀加 \(d\) 本质是一样的。

预处理前后缀的 \(lis\),从左至右枚举后缀的起点,用权值线段树维护前缀 \(lis\) 长度最大值即可。

由于权值过大,离散化后树状数组常数会小一点,但是我懒线段树也很优秀。

时间复杂度 \(O(n \log_2 n + n \log_2 a_i)\)

\(Source\)

#include 
#include 
#include 
int in() {
    int x = 0; char c = getchar(); bool f = 0;
    while (c < '0' || c > '9')
        f |= c == '-', c = getchar();
    while (c >= '0' && c <= '9')
        x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
    return f ? -x : x;
}
templateinline void chk_min(T &_, T __) { _ = _ < __ ? _ : __; }
templateinline void chk_max(T &_, T __) { _ = _ > __ ? _ : __; }
inline int max(int _, int __) { return _ > __ ? _ : __; }

const int N = 2e5 + 5, M = 2e9;
int n, x, a[N], res;
int f[N], len;
int pre[N], suf[N];

struct segment_tree {
    int t[N * 71], c[N * 71][2], rt, tot;
    inline void clear() {
        tot = 0;
    }
    inline int new_node() {
        ++tot;
        t[tot] = c[tot][0] = c[tot][1] = 0;
        return tot;
    }
    void modify(int pos, int k, int tl, int tr, int &p) {
        if (p > tot)
            p = 0;
        if (!p)
            p = new_node();
        chk_max(t[p], k);
        if (tl == tr)
            return chk_max(t[p], k);
        int mid = ((long long)tl + tr) >> 1;
        if (mid >= pos)
            modify(pos, k, tl, mid, c[p][0]);
        else
            modify(pos, k, mid + 1, tr, c[p][1]);
    }
    int query_max(int l, int r, int tl, int tr, int &p) {
        if (p > tot)
            p = 0;
        if (!p)
            return 0;
        if (l <= tl && tr <= r)
            return t[p];
        int mid = ((long long)tl + tr) >> 1;
        if (mid < l)
            return query_max(l, r, mid + 1, tr, c[p][1]);
        if (mid >= r)
            return query_max(l, r, tl, mid, c[p][0]);
        return max(query_max(l, r, tl, mid, c[p][0]),
                   query_max(l, r, mid + 1, tr, c[p][1]));
    }
} T;

void work() {
    f[len = pre[1] = 1] = a[1];
    for (int i = 2, p; i <= n; ++i) {
        p = std::lower_bound(f + 1, f + 1 + len, a[i]) - f;
        if (p == len + 1) {
            f[++len] = a[i];
        } else {
            f[p] = a[i];
        }
        pre[i] = p;
    }

    res = pre[n];
    T.modify(a[n], 1, -M, M, T.rt);
    len = suf[n] = 1, f[n] = a[n];
    for (int i = n - 1, p; i; --i) {
        p = std::upper_bound(f + n - len + 1, f + 1 + n, a[i]) - f - 1;
        if (p == n - len) {
            f[n - len] = a[i];
            ++len;
        } else {
            f[p] = a[i];
        }
        suf[i] = n - p + 1;
        chk_max(res, pre[i] + T.query_max(a[i] - x + 1, M, -M, M, T.rt));
        T.modify(a[i], suf[i], -M, M, T.rt);
    }
}

int main() {
    //freopen("in", "r", stdin);
    freopen("glo.in", "r", stdin);
    freopen("glo.out", "w", stdout);
    n = in(), x = in();
    for (int i = 1; i <= n; ++i)
        a[i] = in();
    work();
    printf("%d\n", res);
    return 0;
}

T2、Mobitel

题目链接

给定一个 \(r \ (r \leq 300)\)\(c \ (c \leq 300)\) 列的矩阵,每个格子里都有一个正整数。
问如果从左上角走到右下角,且每次只能向右或向下走到相邻格子,那么使得路径上所有数的乘积不小于 \(n \ (n \leq 10 ^ 6)\) 的路径有多少条?
由于答案可能很大,所以请输出答案对 \(10^9+7\) 取模的结果。
\(Time \ Limits: 6000 ms\) \(Memory \ Limits: 64 MB\)

\(Sol\)

答案等于 \(\tbinom{r - 1 + c - 1}{c - 1}\) - 乘积 \(\leq n - 1\) 的路径数。
显然有一个暴力 \(dp\)\(f_{i, j, k}\) 表示到 \((i, j)\) 路径乘积为 \(k\) 的方案数,转移是显然的。

考虑优化状态数,记 \(f_{i, j, k}\) 表示到 \((i, j)\) 还有 \(k\) 可以用来被格子上的数除;
\(\lfloor \frac{ \lfloor \frac{S}{x} \rfloor } {y} \rfloor = \lfloor \frac{S}{xy} \rfloor\),所以这样是对的。

学过数论分块的话一定知道,这样的 \(k\) (即 \(\lfloor \frac{n - 1}{i} \rfloor\)) 有一个上界 \(2 \sqrt{n - 1}\)

\(prf\)
\(i \leq \sqrt{N}\) 时,\(\lfloor \frac{N}{i} \rfloor\) 不会超过 \(\sqrt{N}\) 种;
\(i > \sqrt{N}\) 时,\(\lfloor \frac{N}{i} \rfloor < \sqrt{N}\),不会超过 \(\sqrt{N}\) 种。
\(Q.E.D.\)

时间复杂度 \(O(r c \sqrt{n})\),空间复杂度 \(O((r + c) \sqrt{n})\)

\(Source\)

#include 
#include 
#include 
#include 
int in() {
    int x = 0; char c = getchar(); bool f = 0;
    while (c < '0' || c > '9')
        f |= c == '-', c = getchar();
    while (c >= '0' && c <= '9')
        x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
    return f ? -x : x;
}
templateinline void chk_min(T &_, T __) { _ = _ < __ ? _ : __; }
templateinline void chk_max(T &_, T __) { _ = _ > __ ? _ : __; }

const int N = 305, mod = 1e9 + 7;

int R, C, n, mp[N][N], f[2][N + N][2005];
int nn, num[2005], id[1000005];

inline void add(int &_, int __) {
    _ += __, _ = _ < mod ? _ : _ - mod;
}

int qpow(int base, int b, int ret = 1) {
    for (; b; b >>= 1, base = 1ll * base * base % mod)
        if (b & 1)
            ret = 1ll * ret * base % mod;
    return ret;
}

int combination(const int n, const int m) {
    if (n < m)
        return 0;
    int ret = 1;
    for (int i = 2; i <= m; ++i)
        ret = 1ll * ret * i % mod;
    ret = qpow(ret, mod - 2);
    for (int i = n - m + 1; i <= n; ++i)
        ret = 1ll * ret * i % mod;
    return ret;
}

int main() {
    //freopen("in", "r", stdin);
    freopen("mobitel.in", "r", stdin);
    freopen("mobitel.out", "w", stdout);
    R = in(), C = in(), n = in();
    for (int i = 1; i <= R; ++i)
        for (int j = 1; j <= C; ++j)
            mp[i][j] = in();

    for (int i = 1; i < n; i = (n - 1) / ((n - 1) / i) + 1) {
        num[++nn] = (n - 1) / i;
        id[(n - 1) / i] = nn;
    }

    int cur = 0;
    f[1][1][id[(n - 1) / mp[1][1]]] = 1;
    for (int i = 2; i <= R + C - 1; ++i, cur ^= 1) {
        memset(f[cur], 0, sizeof(f[cur]));
        for (int j = std::max(1, i - C + 1); j <= R && i + 1 - j; ++j) {
            for (int k = 1; k <= nn; ++k) {
                add(f[cur][j][id[num[k] / mp[j][i + 1 - j]]], f[cur ^ 1][j][k]);
                add(f[cur][j][id[num[k] / mp[j][i + 1 - j]]], f[cur ^ 1][j - 1][k]);
            }
        }
    }
    cur ^= 1;
    int res = 0;
    for (int i = 1; i <= nn; ++i)
        add(res, f[cur][R][i]);
    printf("%d\n", (combination(R + C - 2, C - 1) - res + mod) % mod);
    return 0;
}

T3、Lottery

题目链接

定义两个序列对应位置上不同的值的个数不超过 \(k\),则可称为 \(k\) 相似。
现在有一个长度为 \(n \ (n \leq 10 ^ 4)\) 的序列 \(a\),它有 \(n−l+1\) 个长度为 \(l \ (l \leq 10 ^ 4)\) 的子串(第 \(i\) 个子串为 \([i, i + l - 1]\))。
\(q \ (q \leq 100)\) 组询问,第 \(j\) 组询问给出一个 \(k_j \ (k_j \leq l)\),求每个子串与多少个其它的子串可称为 \(k_j\) 相似。
\(Memory \ Limits: 32MB\)

\(Sol\)

知道 \([x, x + l - 1], [y, y + l - 1] \ (x \ne y)\) 的相似度,可以 \(O(2)\) (逃) 得出 \([x + 1, x + l], [y + 1, y + l]\) 的相似度。
\(f_{i, j}\) 表示与第 \(i\) 个子串 \(j\) 相似的子串数量,空间复杂度 \(O(n^2)\) ,显然不行;
注意到询问数不超过 \(100\),可以预处理,将询问排序,空间复杂度降为 \(O(nq)\),可以通过。

时间复杂度 \(O(l \log_2 q + n ^ 2 + n q)\)

\(Source\)

#include 
#include 
#include 
int in() {
    int x = 0; char c = getchar(); bool f = 0;
    while (c < '0' || c > '9')
        f |= c == '-', c = getchar();
    while (c >= '0' && c <= '9')
        x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
    return f ? -x : x;
}
templateinline void chk_min(T &_, T __) { _ = _ < __ ? _ : __; }
templateinline void chk_max(T &_, T __) { _ = _ > __ ? _ : __; }

const int N = 1e4 + 5;

struct query {
    int id, k;
} b[105];
int n, L, q, a[N], id[N];
int mp[N][105], res[N];

inline bool cmp_id(const query &i, const query &j) {
    return i.id < j.id;
}

inline bool cmp_k(const query &i, const query &j) {
    return i.k < j.k;
}

void work() {
    std::sort(b + 1, b + 1 + q, cmp_k);
    for (int i = 1; i <= L; ++i)
        id[i] = std::lower_bound(b + 1, b + 1 + q, (query){0, i}, cmp_k) - b;
    for (int i = 1, now; i <= n - L; ++i) {
        now = 0;
        for (int j = 1; j <= L; ++j)
            now += (a[j] != a[j + i]);
        ++mp[1][id[now]];
        ++mp[1 + i][id[now]];
        for (int x = 2, y; x + i <= n - L + 1; ++x) {
            y = x + i;
            now -= (a[x - 1] != a[y - 1]);
            now += (a[x + L - 1] != a[y + L - 1]);
            ++mp[x][id[now]];
            ++mp[y][id[now]];
        }
    }
    for (int i = 1; i <= n - L + 1; ++i)
        for (int j = 1; j <= q; ++j)
            mp[i][j] += mp[i][j - 1];
    std::sort(b + 1, b + 1 + q, cmp_id);
    for (int i = 1; i <= q; ++i) {
        for (int j = 1; j <= n - L + 1; ++j)
            printf("%d ", mp[j][id[b[i].k]]);
        puts("");
    }
}

void input() {
    n = in(), L = in();
    for (int i = 1; i <= n; ++i)
        a[i] = in();

    q = in();
    for (int i = 1; i <= q; ++i)
        b[i] = (query){i, in()};
}

int main() {
    //freopen("in", "r", stdin);
    freopen("lottery.in", "r", stdin);
    freopen("lottery.out", "w", stdout);
    input();
    work();
    return 0;
}

你可能感兴趣的:(「2019纪中集训Day20」解题报告)