梦熊 NOIP 十三连测模拟赛记录

By hhoppitree.

Round 1 A. Apair

题目大意

给定平面直角坐标系上的 n 个整点,求任意两个不同的点的曼哈顿距离与欧几里得距离的比的最大值,多组询问。
数据范围:T10,n1051s/512MB

思路分析

考虑我们就是要让连线段的角度最接近 π43π4,将平面直角坐标系旋转对应角度后也就是要求连线段与 x 轴夹角最小的两个点。

按旋转后的 y 坐标排序后,最优解一定可以在两个点相邻的时候取到,证明可以自己画画图。

最终的时间复杂度为单组询问 O(nlogn)

代码呈现

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 5;
const long double pi = acos(-1.0);

int n;
pair<int, int> c[N];

int cmp1(pair<int, int> x, pair<int, int> y) {
    return x.first + x.second < y.first + y.second;
}

int cmp2(pair<int, int> x, pair<int, int> y) {
    return x.first - x.second < y.first - y.second;
}

long double calc() {
    long double res = 0;
    for (int i = 1; i < n; ++i) {
        int x = abs(c[i].first - c[i + 1].first), y = abs(c[i].second - c[i + 1].second);
        res = max(res, (x + y) / hypot((long double)x, (long double)y));
    }
    return res;
}

signed main() {
    freopen("Apair.in", "r", stdin);
    freopen("Apair.out", "w", stdout);
    int T; scanf("%d", &T);
    while (T--) {
        scanf("%d", &n);
        for (int i = 1; i <= n; ++i) scanf("%d%d", &c[i].first, &c[i].second);
        sort(c + 1, c + n + 1, cmp1);
        long double res = calc();
        sort(c + 1, c + n + 1, cmp2);
        printf("%.20Lf\n", max(res, calc()));
    }
    return 0;
}

Round 1 D. Dmagic

题目大意

有两个长度为 n 的数组 ab,初始时 a 给定,b 均为 0,每次可以选择数组 b 的前缀或者是后缀加 1,花费为选中元素个数,求最少花费使得 bi 均不小于 ai,多组询问。
数据范围:T10,n1051s/512MB

思路分析

考虑最终花费即为 b 数组元素之和。

也就是说,我们要找到一个“可生成的”序列 b,使得 biai,且在此基础上,i=1nbi 最小。

不妨将 b 数列从初始全为 0 增加视作是一次操作将前后缀减 1,求其是否能够变成全 0 数列。

如何去刻画一个序列“可被生成”呢?注意到,如果 bi>bi+1,那么至少要对 [1,i] 进行 bibi+1 次前缀操作;反之,如果 bi<bi+1,那么至少要对 [i+1,n] 进行 bi+1bi 次后缀操作。

在这些操作进行完后,必然有 b 中元素全相等,此时若有 bi0 则序列 b 就是可以被生成的。

考虑用一个简洁的式子去概括,考虑最终 b1bn 的值,则有 b1i=1n1max(bibi+1,0)=bni=1n1max(bi+1bi,0)0,即 b1+bni=1n1|bibi+1|0

考虑去除 b1,bn 的特殊性,如果假设 b0=bn+1=C,其中 C 为一充分大常数,则有 i=0n|bibi+1|2C

现在我们得到了简洁的条件,如果初始的 b 不合法,如何在 i=1nbi 尽可能小的要求下,将其变得合法呢?

D=i=0n|bibi+1|2C,将 (i,bi)(0in+1) 画成柱状图,我们一次可以将一个“低谷”给填平一格(也就是将满足 bl1>bl=bl+1==br<br+1[l,r] 区间加 1),此时 DD2

建立出 b 的笛卡尔树,则我们可以算出每个位置对应的一次操作的代价(也即区间长度),贪心选取最小的 D2 个即可,时间复杂度为单组询问 O(nlogn)

代码呈现

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 5;

int a[N];
pair<int, int> f[20][N];

int query(int l, int r) {
    int k = log2(r - l + 1);
    return max(f[k][l], f[k][r - (1 << k) + 1]).second;
}

vector< pair<int, int> > bt;

void dfs(int l, int r) {
    if (l > r) return;
    if (l == r) {
        bt.push_back({1, min(a[l - 1], a[r + 1]) - a[l]});
        return;
    }
    int m = query(l, r);
    dfs(l, m - 1), dfs(m + 1, r);
    bt.push_back({r - l + 1, min(a[l - 1], a[r + 1]) - a[m]});
}

signed main() {
    freopen("Dmagic.in", "r", stdin);
    freopen("Dmagic.out", "w", stdout);
    int T; scanf("%d", &T);
    while (T--) {
        int n; scanf("%d", &n);
        for (int i = 1; i <= n; ++i) {
            scanf("%d", &a[i]);
            f[0][i] = make_pair(a[i], i);
        }
        for (int i = 1; (1 << i) <= n; ++i) {
            for (int j = 1; j + (1 << i) - 1 <= n; ++j) {
                f[i][j] = max(f[i - 1][j], f[i - 1][j + (1 << (i - 1))]);
            }
        }
        bt.clear();
        a[0] = a[n + 1] = 1e9 + 1;
        dfs(1, n);
        long long s = 0, res = 0;
        for (int i = 0; i <= n; ++i) s += abs(a[i] - a[i + 1]);
        for (int i = 1; i <= n; ++i) res += a[i];
        s -= (int)2e9 + 2;
        sort(bt.begin(), bt.end());
        reverse(bt.begin(), bt.end());
        s >>= 1;
        while (s > 0) {
            long long t = min(s, (long long)bt.back().second);
            s -= t, bt.back().second -= t, res += t * bt.back().first;
            if (!bt.back().second) bt.pop_back();
        }
        printf("%lld\n", res);
    }
    return 0;
}

Round 2 D. 魔法阵

题目大意

给定 n,m,求有多少个为 kk 任意)的矩阵 a 满足每个元素均 m 且主对角线上元素和不超过 n,且任意选 k 个不同行同列的元素,这 k! 种选法元素的和相等,对 998244353 取模,多组询问。
数据范围:T50,n,m1051s/256MB

思路分析

不妨枚举 k[1,nm],则条件三等价于存在长度均为 k 的序列 b,c,使得 ai,j=bi+cj

但是这样 a 可能会被算重,所以不妨令 min{bi}=0,此时 b,c 与合法的 a 构成了双射。

问题转化为了对 b,c 的计数,我们有 i=1nbi+i=1ncin,以及 min{bi}=0,min{ci}m

min{bi} 的限制容斥后用插板法处理即可,单组询问时间复杂度 O(nm)

代码呈现

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 5, P = 998244353;

int fac[N], iFac[N];

int C(int n, int m) {
    if (!n) return !m;
    if (m < 0) return 0;
    int a = n + m - 1, b = n - 1;
    return 1ll * fac[a] * iFac[a - b] % P * iFac[b] % P;
}

signed main() {
    freopen("magic.in", "r", stdin);
    freopen("magic.ans", "w", stdout);
    for (int i = fac[0] = iFac[0] = 1; i < N; ++i) {
        fac[i] = 1ll * fac[i - 1] * i % P;
        iFac[i] = (i == 1 ? 1 : 1ll * (P - P / i) * iFac[P % i] % P);
    }
    for (int i = 1; i < N; ++i) iFac[i] = 1ll * iFac[i] * iFac[i - 1] % P;
    int T; scanf("%d", &T);
    while (T--) {
        int n, m, res = 0; scanf("%d%d", &n, &m);
        for (int k = 1; k * m <= n; ++k) {
            res = ((long long)res + C(k + k + 1, n - k * m) - C(k + k + 1, n - k * m - k) + P) % P;
        }
        printf("%d\n", res);
    }
    return 0;
}

Round 3 D. Tree

题目大意

给定一棵 n 个点的树,每个点上有一个按钮,初始时小 A 在 1 号节点,想要到达 n 号节点,每次你可以按一个非小 A 所处位置上的未被按过的按钮,小 A 会沿着最短路径走过一条边,求在所有点都按过一次的情况下,被小 A 经过至少一次的点的个数的最小值或说明无解。
数据范围:n2×1051s/512MB

思路分析

对树黑白染色,显然一次操作会让小 A 到另一个颜色的点上去,所以如果 ndis(1,n) 的奇偶性不同直接输出无解即可。

否则,我们先考虑小 A 能否只经过 1=p0p1p2pd=n 上的节点,我们发现一个必要条件是不会“卡死”在一个点上,也就是我们可以通过反复横跳来消耗掉过多的步数。

具体地,假设路径上的第 i 个点对应的子树中有 ci 个节点,那么我们有必要条件 0id,2(cii)nd,也即 cind2+i

解释一下这个公式,前面先直接走到这个节点,再左右横跳留够足够的步数走到终点。

若上述条件对于所有 i 均满足,可以归纳证明必然存在合法操作方式。

否则,我们证明不符合这个条件的 i 最多只有一个,证明使用反证法:设 i<j 均不满足,则 ci+cj>nd2+i+nd2+j,则 ci+cj>nd+i+j,而 ci+cjn(dj),故 n(dj)>nd+i+j,矛盾!故不符合条件的 i 最多只有一个。

考虑最终答案所呈现的连通块,此时仍然可以计算出更改后的 c,显然我们只在不合法的 i 对应的子树内进行扩展,也就是在 ci 内部反复横跳来削减 c

假设 cici 而其余 c 不变,则有 2(cii)nd(cici)cici 为通过反复横跳后等价的削减的点数),化简得 cinmci+2i

我们考虑使用 dfs 从 pi 开始进行扩展,假设它的儿子的子树大小构成的序列为 s1s2,如果 s1 占了至少一半,那么肯定只向 s1 对应的子节点 v 进行扩展,由此可以算出新的 cv 的上限,继续递归计算即可。

如果不存在绝对众数,那么我们就按照子树从大到小的顺序剥离子节点(此时必然没有向下递归的必要),这是因为在子节点继续递归来减小对应子节点的 ci 必然可以在 x 的邻域内解决,总时间复杂度 O(nlogn),使用桶排序可以做到线性。

代码呈现

#include <bits/stdc++.h>

using namespace std;

const int N = 2e5 + 5;

vector<int> G[N];
int n, dep[N], hv[N], siz[N];

void dfs(int x, int fa = -1) {
    hv[x] = (x == n);
    for (auto v : G[x]) if (v != fa) {
        dep[v] = dep[x] + 1, dfs(v, x), hv[x] |= hv[v];
    }
}

vector< pair<int, int> > tp;

int getC(int x) {
    int sz = 1;
    for (auto v : G[x]) if (dep[v] > dep[x]) {
        sz += getC(v);
    }
    siz[x] = sz;
    if (hv[x]) {
        tp.push_back(make_pair(x, sz));
        sz = 0;
    }
    return sz;
}

int lim, res;

void calc(int x) {
    ++res;
    int w = 0;
    for (auto v : G[x]) {
        if (!hv[v] && dep[v] > dep[x] && siz[v] > siz[w]) {
            w = v;
        }
    }
    if (siz[w] + siz[w] >= siz[x]) {
        if (siz[w] - (siz[x] - siz[w]) <= lim) return;
        lim += (siz[x] - siz[w]);
        calc(w);
    } else {
        vector<int> p;
        for (auto v : G[x]) {
            if (!hv[v] && dep[v] > dep[x]) {
                p.push_back(siz[v]);
            }
        }
        sort(p.rbegin(), p.rend());
        int t = -siz[x];
        for (auto x : p) {
            t += (x << 1);
            if (t >= -lim) break;
            ++res;
        }
    }
}

signed main() {
    freopen("tree.in", "r", stdin);
    freopen("tree.out", "w", stdout);
    scanf("%d", &n);
    for (int i = 1, x, y; i < n; ++i) {
        scanf("%d%d", &x, &y);
        G[x].push_back(y), G[y].push_back(x);
    }
    dfs(1);
    int d = dep[n];
    if ((n - d) & 1) return 0 & puts("-1");
    getC(1);
    reverse(tp.begin(), tp.end());
    assert((int)tp.size() == d + 1);
    int ok = -1;
    for (int i = 0; i <= d; ++i) {
        if (tp[i].second > ((n - d) >> 1) + i) assert(!~ok), ok = i;
    }
    if (!~ok) return 0 & printf("%d\n", d + 1);
    lim = n - d + ok + ok - tp[ok].second;
    calc(tp[ok].first);
    printf("%d\n", d + 1 + res);
    return 0;
}

Round 4 D.

题目大意

有一棵有根树,初始时只有一个根节点,你需要支持 m 次操作,每次操作为在一个节点上挂一个叶子,或询问一个子树的自同构数量对 998244353 取模后的结果。
数据范围:m2×1056s/1024MB

思路分析

考虑记 fi 为节点 i 的子节点的每个等价类的大小的阶乘的乘积,则询问时只需输出子树内的 f 的积即可。

考虑如何动态维护 f,首先一个子问题是如何找到容易的一个哈希函数,再在每个点上维护子节点的哈希值构成的可重集。

hx=G(depx)(1+vsonxhv),则可以使用类似动态 dp 的方式维护 hx,使用树链剖分可以做到 O(mlog2m)

如何快速更新集合呢?我们发现一个点到根的路径经过的轻边只有 O(logn) 条(n 为树的点数),所以我们只能维护所有轻儿子哈希值构成的可重集,每次更新的时候都再把重儿子考虑进来来计算贡献。

轻边是容易更新的,我们现在只要找到所有会改变的 fv 即可(当前插入的 xv 的重儿子的子树内)。

发现一个必要条件是 sizv>2sizsonv,所以要更新的 fv 的个数是 O(logn) 的,我们只需要对每条重链开个线段树,支持区间加同一个整数,每次把所有 >0 的位置提取出来即可。

总时间复杂度 O(mlog2m)

代码呈现

#include <bits/stdc++.h>

using namespace std;

const int N = 3e5 + 5, P = 998244353;

struct Mod {
    long long m, p;
    void init(int pp) { m = ((__int128)1 << 64) / pp; p = pp; }
    long long operator ()(long long x) {
        return x - ((__int128(x) * m) >> 64) * p;
    }
} mod;

int ksm(int x, int y = P - 2) {
    int res = 1;
    while (y) {
        if (y & 1) res = mod(1ll * res * x);
        x = mod(1ll * x * x);
        y >>= 1;
    }
    return res;
}

vector<int> G[N];
int n = 1, opt[N], id[N], inv[N];
int dep[N], fa[N], siz[N], son[N], top[N], dfn[N], rev[N], lst[N];

void dfs1(int x) {
    siz[x] = 1;
    for (auto v : G[x]) {
        dep[v] = dep[x] + 1, fa[v] = x;
        dfs1(v);
        siz[x] += siz[v];
        (siz[v] > siz[son[x]]) && (son[x] = v);
    }
}

void dfs2(int x) {
    dfn[x] = ++dfn[0], lst[top[x]] = x, rev[dfn[0]] = x;
    if (!son[x]) {
        return;
    }
    top[son[x]] = top[x];
    dfs2(son[x]);
    for (auto v : G[x]) {
        if (v != son[x]) dfs2(top[v] = v);
    }
}

namespace DS1 {
    int s[N];

    void Init() {
        for (int i = 1; i <= n; ++i) s[i] = 1;
    }

    int query(int x) {
        int res = 1;
        for (; x; x -= x & -x) res = 1ll * res * s[x] % P;
        return res;
    }

    int query(int l, int r)  {
        return 1ll * query(r) * ksm(query(l - 1)) % P;
    }

    void modify(int x, int y) {
        for (; x <= n; x += x & -x) s[x] = 1ll * s[x] * y % P;
    }
}

vector<int> ids;

namespace DS4 {
    int mx[1 << 20], lz[1 << 20];

    void modify(int k, int l, int r, int x, int y, int v) {
        if (l > y || r < x) return;
        if (l >= x && r <= y) {
            mx[k] += v, lz[k] += v;
            return;
        }
        if (lz[k]) {
            mx[k << 1] += lz[k];
            lz[k << 1] += lz[k];
            mx[k << 1 | 1] += lz[k];
            lz[k << 1 | 1] += lz[k];
            lz[k] = 0;
        }
        int mid = (l + r) >> 1;
        modify(k << 1, l, mid, x, y, v);
        modify(k << 1 | 1, mid + 1, r, x, y, v);
        mx[k] = max(mx[k << 1], mx[k << 1 | 1]);
    }

    void query(int k, int l, int r, int x, int y) {
        if (l > y || r < x || mx[k] <= 0) return;
        if (l == r) {
            ids.push_back(rev[l]);
            return;
        }
        if (lz[k]) {
            mx[k << 1] += lz[k];
            lz[k << 1] += lz[k];
            mx[k << 1 | 1] += lz[k];
            lz[k << 1 | 1] += lz[k];
            lz[k] = 0;
        }
        int mid = (l + r) >> 1;
        query(k << 1, l, mid, x, y);
        query(k << 1 | 1, mid + 1, r, x, y);
    }

    void active(int x) {
        while (x) {
            int z = top[x];
            modify(1, 1, n, dfn[z] + 1, dfn[x], -1);
            if (son[x]) modify(1, 1, n, dfn[son[x]], dfn[son[x]], 1);
            x = fa[z];
        }
    }
}

void getPath(int x) {
    while (x > 1) {
        if (top[x] != 1) {
            ids.push_back(top[x]);
        }
        DS4::query(1, 1, n, dfn[top[x]] + 1, dfn[x]);
        x = fa[top[x]];
    }
}

mt19937 rnd;
int vl[N];

namespace DS3 {
    int p;
    pair<int, int> z[1 << 20];

    void Init() {
        for (p = 1; p < n + 2; p <<= 1);
        for (int i = 1; i <= p + n; ++i) z[i] = {1, 0};
    }

    pair<int, int> operator + (pair<int, int> x, pair<int, int> y) {
        return {mod(1ll * x.first * y.first), mod((long long)x.first * y.second + x.second)};
    }

    void modifyK(int x, int y) {
        for (z[x + p].first = (long long)z[x + p].first * y % P, x = (x + p) >> 1; x; x >>= 1) {
            z[x] = z[x << 1] + z[x << 1 | 1];
        }
    }

    void modify(int x, int y, int w) {
        for (z[x + p] = {y, w}, x = (x + p) >> 1; x; x >>= 1) {
            z[x] = z[x << 1] + z[x << 1 | 1];
        }
    }

    int query(int l, int r) {
        pair<int, int> L = make_pair(1, 0), R = make_pair(1, 0);
        for (l += p - 1, r += p + 1; l ^ r ^ 1; ) {
            if (~l & 1) L = L + z[l ^ 1];
            if (r & 1) R = z[r ^ 1] + R;
            l >>= 1, r >>= 1;
        }
        L = L + R;
        return (L.first + L.second) % P;
    }

    int getHash(int x) {
        int y = lst[top[x]];
        int res = query(dfn[x], dfn[y]);
        return res;
    }

    void active(int x) {
        int tx = x; x = fa[x];
        vector< pair<int, int> > tz;
        while (x) {
            int tp = top[x];
            if (tp == 1) break;
            int V = ksm(getHash(tp));
            tz.push_back({dfn[fa[tp]], V});
            x = fa[tp];
        }
        for (auto t : tz) {
            modifyK(t.first, t.second);
        }
        x = tx;
        modify(dfn[x], vl[dep[x]], vl[dep[x]]);
        while (x) {
            int tp = top[x];
            if (tp == 1) break;
            int V = getHash(tp);
            modifyK(dfn[fa[tp]], V);
            x = fa[tp];
        }
    }
}

unordered_map<int, int> M[N];
int usd[N];

void active(int x) {
    DS3::active(x);
    DS4::active(x);
}

int Fac[N], iFac[N];

signed main() {
    freopen("tree.in", "r", stdin);
    freopen("tree.out", "w", stdout);
    int q; scanf("%d", &q);
    mod.init(P);
    for (int i = 1, x; i <= q; ++i) {
        scanf("%d%d", &opt[i], &x);
        if (opt[i] == 0) G[x].push_back(id[i] = ++n);
        else id[i] = x;
    }
    for (int i = Fac[0] = iFac[0] = 1; i <= n; ++i) {
        Fac[i] = 1ll * Fac[i - 1] * i % P;
        inv[i] = (i == 1 ? 1 : 1ll * (P - P / i) * inv[P % i] % P);
        iFac[i] = 1ll * iFac[i - 1] * inv[i] % P;
    }
    dfs1(dep[1] = 1), dfs2(top[1] = 1);
    for (int i = 1; i <= n; ++i) {
        vl[i] = rnd() % P;
    }
    DS1::Init(), DS4::active(1), DS3::Init(), DS3::active(1);
    for (int i = 1; i <= q; ++i) {
        if (opt[i] == 0) {
            usd[id[i]] = 1;
            ids.clear();
            getPath(fa[id[i]]);
            for (auto z : ids) {
                int val = DS3::getHash(z);
                int ts = (usd[son[fa[z]]] && val == DS3::getHash(son[fa[z]]));
                DS1::modify(dfn[fa[z]], 1ll * iFac[M[fa[z]][val] + ts] * Fac[M[fa[z]][val] + ts - 1] % P);
                if (z != son[fa[z]]) {
                    --M[fa[z]][val];
                }
            }
            active(id[i]);
            ids.clear();
            getPath(id[i]);
            for (auto z : ids) {
                int val = DS3::getHash(z);
                int ts = (z != son[fa[z]] && usd[son[fa[z]]] && val == DS3::getHash(son[fa[z]]));
                DS1::modify(dfn[fa[z]], 1ll * iFac[M[fa[z]][val] + ts] * Fac[M[fa[z]][val] + ts + 1] % P);
                if (z != son[fa[z]]) {
                    ++M[fa[z]][val];
                }
            }
        } else {
            printf("%d\n", DS1::query(dfn[id[i]], dfn[id[i]] + siz[id[i]] - 1));
        }
    }
    return 0;
}

Round 7 C. mod

题目大意

给定长度为 n 的数组 a,每次可以选定正整数 x,并令 aiaimodx,求最终可能得到的 a 的数量对 998244353 取模后的结果。
数据范围:n,ai5001s/512MB

思路分析

下文视 n,ai 同阶。

不妨令 ai 互不相同且递增,先来考虑一下当 ai=i(0in) 时可能的 a 的数量。

如何判定一个 a 是否合法,我们观察到 a 合法当且仅当将 (i,ai) 画在平面直角坐标系上,这些点构成了若干条斜率为 1 的线段,且每条线段的左下端点均在 x 轴上,每个线段经过的点的 x 坐标都是连续的,并且第一条线段的长度是所有线段中最大的。

这样就可以 dp 了,先枚举第一条线段的长度 k,然后记 fi,j(jk) 表示 ai=j 可能的 a 的个数,转移分为 fi,jfi+1,j+1fi,jfi+1,0 两种,这样就可以解决 ai=i 的情况了。

一般情况下,可以同样直接枚举第一条线段的长度(注意不要算重了),然后转移是类似的,只是层间的转移条件要稍微更改一下,时间复杂度是 O(n3) 的。

代码呈现

#include <bits/stdc++.h>

using namespace std;

const int N = 505, P = 998244353;

int f[N][N], g[N][N];

signed main() {
    freopen("mod.in", "r", stdin);
    freopen("mod.out", "w", stdout);
    int n; scanf("%d", &n);
    vector<int> a;
    for (int i = 1, x; i <= n; ++i) {
        scanf("%d", &x), a.push_back(x);
    }
    sort(a.begin(), a.end()), a.erase(unique(a.begin(), a.end()), a.end());
    int res = 1;
    for (int i = 0; i < a.back(); ++i) {
        int wh;
        for (int j = 0; ; ++j) if (a[j] > i) {
            wh = j; break;
        }
        if (a[wh] - i - 1 > i) continue;
        memset(f, 0, sizeof(f));
        memset(g, 0, sizeof(g));
        f[wh][a[wh] - i - 1] = 1;
        for (int j = wh; j < (int)a.size(); ++j) {
            for (int k = 500; ~k; --k) {
                ((g[j][k] += g[j][k + 1]) >= P) && (g[j][k] -= P);
                ((f[j][k] += g[j][k]) >= P) && (f[j][k] -= P);
            }
            if (j == (int)a.size() - 1) break;
            int D = a[j + 1] - a[j];
            for (int k = 0; k <= 500; ++k) if (f[j][k]) {
                if (k + D <= i) ((f[j + 1][k + D] += f[j][k]) >= P) && (f[j + 1][k + D] -= P);
                ((g[j + 1][min(D - 1, i)] += f[j][k]) >= P) && (g[j + 1][min(D - 1, i)] -= P);
            }
        }
        for (int j = 0; j <= 500; ++j) ((res += f[a.size() - 1][j]) >= P) && (res -= P);
    }
    printf("%d\n", res);
    return 0;
}

Round 7 D. divide

题目大意

给定长度为 n 的数组 a,你需要对于每个 i[1,n] 求出,如果将 ai0,有多少种将 a 划分为若干个子串的方式满足每一个子串的长度均至少为其中元素的最大值,对 998244353 取模。
数据范围:n2×1051s/512MB

思路分析

如果没有这个修改,只要求答案的话,可以使用 cdq 分治简单解决,对于跨过中点的询问枚举最大值在左侧还是右侧,做个二维偏序即可。

这样还可以顺便求出 a 的每个前缀和后缀的答案,分别记为 fg

对于每个 i,我们考虑枚举它所在的区间的左右端点 l,r,在 [l,r] 合法的条件下,这样的 [l,r] 就对 ansi 有着 fl1×gr+1 的贡献。

考虑仍然在 cdq 分治的基础上动手脚,我们枚举 [l,r] 跨过了哪个区间的中点(这样的区间只有 O(logn) 个)。

对于每层内的 i 合并计算(也就是先枚举中点被划分的区间),列出式子后发现贡献仍然是一个二维偏序的形式,简单做做就好了,时间复杂度为 O(nlog2n)

代码呈现

#include <bits/stdc++.h>

using namespace std;

const int N = 2e5 + 5, P = 998244353;

int n, a[N], f[N], g[N], h[N], res[N], s[N], suf[N];
vector<int> p[N];

void modify(int x, int y) {
    for (; x <= n; x += x & -x) ((s[x] += y) >= P) && (s[x] -= P);
}

int query(int x) {
    int S = 0;
    for (; x; x -= x & -x) ((S += s[x]) >= P) && (S -= P);
    return S;
}

void dfs1(int l, int r) {
    if (l > r) return;
    if (l == r) {
        if (a[l] == 1) ((f[l] += f[l - 1]) >= P) && (f[l] -= P);
        return;
    }
    int mid = (l + r) >> 1;
    dfs1(l, mid);
    for (int i = mid + 1; i <= r; ++i) p[i].clear();
    for (int i = mid, o = a[i]; i >= l; --i) {
        o = max(o, a[i]);
        if (i + o - 1 <= r) p[max(i + o - 1, mid + 1)].push_back(i);
    }
    for (int i = mid + 1, o = a[i]; i <= r; ++i) {
        for (auto v : p[i]) if (f[v - 1]) modify(v, f[v - 1]);
        o = max(o, a[i]);
        if (i - o + 1 >= l) ((f[i] += query(min(i - o + 1, mid))) >= P) && (f[i] -= P);
    }
    for (int i = mid + 1; i <= r; ++i) {
        for (auto v : p[i]) if (f[v - 1]) modify(v, P - f[v - 1]);
    }
    dfs1(mid + 1, r);
}

void dfs2(int l, int r, int d) {
    if (l >= r) return;
    int mid = (!d ? (l + r) >> 1 : n - (((n - l + 1) + (n - r + 1)) >> 1));
    dfs2(l, mid, d), dfs2(mid + 1, r, d);
    for (int i = mid + 1; i <= r; ++i) p[i].clear();
    for (int i = mid, o = a[i]; i >= l; --i) {
        o = max(o, a[i]);
        if (i + o - 1 <= r) p[max(i + o - 1, mid + 1)].push_back(i);
    }
    for (int i = mid + 1, o = -1, wh = 0, o2 = -1; i <= r; ++i) {
        for (auto v : p[i]) if (f[v - 1]) modify(v, f[v - 1]);
        if (a[i] > o) o2 = o, o = a[i], wh = i;
        else o2 = max(o2, a[i]);
        int tf = 0;
        if (i - o + 1 >= l) {
            int V = 1ll * (tf = query(min(i - o + 1, mid))) * g[i + 1] % P;
            suf[i] = V;
        } else {
            tf = suf[i] = 0;
        }
        if (i - o2 + 1 >= l) {
            h[wh] = (h[wh] + 1ll * (query(min(i - o2 + 1, mid)) - tf + P) * g[i + 1]) % P;
        }
    }
    for (int i = r; i >= mid + 1; --i) {
        if (i != r) ((suf[i] += suf[i + 1]) >= P) && (suf[i] -= P);
        ((h[i] += suf[i]) >= P) && (h[i] -= P);
    }
    for (int i = mid + 1; i <= r; ++i) {
        for (auto v : p[i]) if (f[v - 1]) modify(v, P - f[v - 1]);
    }
}

signed main() {
    freopen("divide.in", "r", stdin);
    freopen("divide.out", "w", stdout);
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
    reverse(a + 1, a + n + 1), f[0] = 1, dfs1(1, n), reverse(f, f + n + 2), memcpy(g, f, sizeof(g)), memset(f, 0, sizeof(f)), reverse(a + 1, a + n + 1), f[0] = 1, dfs1(1, n);
    for (int i = 1; i <= n; ++i) h[i] = 1ll * f[i - 1] * g[i + 1] % P;
    dfs2(1, n, 0), memcpy(res, h, sizeof(res)), reverse(f, f + n + 2), reverse(g, g + n + 2), swap(f, g), reverse(a + 1, a + n + 1), memset(h, 0, sizeof(h)), dfs2(1, n, 1), reverse(h + 1, h + n + 1);
    for (int i = 1; i <= n; ++i) (((res[i] += h[i]) >= P) && (res[i] -= P)), printf("%d%c", res[i], " \n"[i == n]);
    return 0;
}

Round 8 D. e

题目大意

给定 n 个三元有序数对 (ai,bi,ci),求它们任意排列后所需的最少操作次数使得 a,b,c 均不降,其中一次操作为选择一个元素加 1
数据范围:n2501s/512MB

思路分析

考虑改为最小化 i=1nai+bi+ci,最终让答案减去初始时的 i=1nai+bi+ci 即可。

将每个点视作三维坐标画在空间直角坐标系上,那么合法当且仅当存在一条每次只向右前上的折线经过每一个点。

如果这条折线确定了,那么每个 (ai,bi,ci) 对于答案的贡献是容易计算的(可以视作一个投影),那么使用 dp 计算 fi,j,k,表示离散化后,当前折线 x 坐标为第 i 小的 ay 坐标为第 j 小的 bz 坐标为第 k 小的 c 时已确定投影的点最小代价和,转移时枚举下一步往哪一个方向走即可。

时间复杂度为 O(n3)

代码呈现

#include <bits/stdc++.h>

using namespace std;

const int N = 255;

int a[N], b[N], c[N];
long long f[N][N][N];
int sa[N][N][N], sb[N][N][N], sc[N][N][N];

signed main() {
    freopen("d.in", "r", stdin);
    freopen("d.out", "w", stdout);
    int n; scanf("%d", &n);
    vector<int> la, lb, lc;
    la.push_back(0), lb.push_back(0), lc.push_back(0);
    long long sum = 0;
    for (int i = 1; i <= n; ++i) {
        scanf("%d%d%d", &a[i], &b[i], &c[i]);
        la.push_back(a[i]);
        lb.push_back(b[i]);
        lc.push_back(c[i]);
        sum += (long long)a[i] + b[i] + c[i];
    }
    sort(la.begin(), la.end());
    sort(lb.begin(), lb.end());
    sort(lc.begin(), lc.end());
    la.erase(unique(la.begin(), la.end()), la.end());
    lb.erase(unique(lb.begin(), lb.end()), lb.end());
    lc.erase(unique(lc.begin(), lc.end()), lc.end());
    for (int i = 1; i <= n; ++i) {
        int ta = lower_bound(la.begin(), la.end(), a[i]) - la.begin();
        int tb = lower_bound(lb.begin(), lb.end(), b[i]) - lb.begin();
        int tc = lower_bound(lc.begin(), lc.end(), c[i]) - lc.begin();
        ++sa[ta][tb][tc], ++sb[ta][tb][tc], ++sc[ta][tb][tc];
    }
    for (int i = 0; i < (int)la.size(); ++i) {
        for (int j = 0; j < (int)lb.size(); ++j) {
            for (int k = 0; k < (int)lc.size(); ++k) {
                sa[i][j][k] += (j ? sa[i][j - 1][k] : 0) + (k ? sa[i][j][k - 1] : 0) - (j && k ? sa[i][j - 1][k - 1] : 0);
                sb[i][j][k] += (i ? sb[i - 1][j][k] : 0) + (k ? sb[i][j][k - 1] : 0) - (i && k ? sb[i - 1][j][k - 1] : 0);
                sc[i][j][k] += (i ? sc[i - 1][j][k] : 0) + (j ? sc[i][j - 1][k] : 0) - (i && j ? sc[i - 1][j - 1][k] : 0);
            }
        }
    }
    memset(f, 0x3f, sizeof(f));
    f[0][0][0] = 0;
    for (int i = 0; i < (int)la.size(); ++i) {
        for (int j = 0; j < (int)lb.size(); ++j) {
            for (int k = 0; k < (int)lc.size(); ++k) {
                if (i + 1 != la.size()) {
                    f[i + 1][j][k] = min(f[i + 1][j][k], f[i][j][k] + ((long long)la[i + 1] + lb[j] + lc[k]) * sa[i + 1][j][k]);
                }
                if (j + 1 != lb.size()) {
                    f[i][j + 1][k] = min(f[i][j + 1][k], f[i][j][k] + ((long long)la[i] + lb[j + 1] + lc[k]) * sb[i][j + 1][k]);
                }
                if (k + 1 != lc.size()) {
                    f[i][j][k + 1] = min(f[i][j][k + 1], f[i][j][k] + ((long long)la[i] + lb[j] + lc[k + 1]) * sc[i][j][k + 1]);
                }
            }
        }
    }
    printf("%lld\n", f[la.size() - 1][lb.size() - 1][lc.size() - 1] - sum);
    return 0;
}

Round 9 D. 电力公司

题目大意

在直线 y=1y=1 的所有 (x,y)(x[1,n]) 上都有一座房子(y=1)或电塔 y=1,激活 (i,1) 上的房子需要 ui 代价,激活 (i,1) 上的电塔需要 vi 代价,你可以在激活的第 x 座房子和第 y 座电塔连线可以获得 wx,y 的收益,要求连线仅在建筑物处相交,q 次询问,每次给定两个子区间 [a,b],[c,d],询问在只考虑 [a,b] 内的房子和 [c,d] 内的电塔时的最大收益。
数据范围:n300,q105,ui,vi,wx,y>02s/512MB

思路分析

考虑全局询问怎么做,我们可以使用动态规划来解决,一种合理的状态设计是:fi,j 表示考虑到房子 i 水塔 j未钦定选择的方案数,gi,j 表示考虑到房子 i 钦定选择水塔 j 未钦定选择的方案数,hi,j 表示考虑到房子 i 未钦定选择水塔 j 钦定选择的最优答案。

这里的未钦定可以在后续操作中被选到,没有房子水塔均被钦定选择的状态的原因是均可以被表示为 gh(相当于可以假设一座建筑会无用),为了后续操作方便,我们在这里一种方案可能会对应多种转移路径。

在转移中,为了增加转移路径数量,我们不妨假设一次只进行一步操作(移动上端点,移动下端点,激活房子,激活电塔)或扰动尽量小,这样可以得到 f,g,h 的状态转移方程:

fi1,jfi,jfi,j1fi,jgi1,jfi,jhi,j1fi,jfi,juigi,jgi,j1gi,jgi,j1vj1+wi,j1gi,jhi,j1ui+wi,j1gi,jfi,jvjhi,jgi1,jvj+wi1,jhi,jhi1,jhi,jhi1,jui1+wi1,jhi,j

考虑多组询问,这样,问题就转化为了 DAG 上求 fa,cfb+1,d+1 的最长路。

此时“宽松”地设计状态的好处就出来了:如果选择了至少一座建筑,那么对于任意 aib,必然存在 cjd 使得最优解经过了状态 hi,j;对于任意 cjd,必然存在 aib 使得最优解经过了状态 gi,j

所以,我们使用分治的方式,每次选取区间长度较大的那一维(有四个参数 l1,r1,l2,r2,我们比较 r1l1r2l2),然后我们枚举经过了哪个点更新即可。

例如,假如 r1l1>r2l2,设 mid=l1+r12 我们枚举经过的 hmid,j,求出它到拓扑序比它大的每个点的最长路以及拓扑序小的每个点到它的最长路,而所有 [a,b] 跨过 mid 的询问都可以一同计算,这样单组分治的时间复杂度是 O(len3) 的。

故总时间复杂度为 T(n)=4T(n2)+O(n3),总时间复杂度为 O(n3+qn)

代码呈现

#include <bits/stdc++.h>

using namespace std;

const int N = 305, Q = 1e5 + 5;

int ox[N], oy[N], ow[N][N], res[Q], f[N][N], g[N][N], h[N][N], tf[N][N], tg[N][N], th[N][N];

void solve(int l1, int r1, int l2, int r2, vector< pair< pair< pair<int, int>, pair<int, int> >, int> > qr) {
    if (qr.empty()) return;
    if (r1 - l1 > r2 - l2) {
        int mid = (l1 + r1) >> 1;
        vector< pair< pair< pair<int, int>, pair<int, int> >, int> > qr1, qr2, tqr;
        for (auto [x, y] : qr) {
            if (x.first.second < mid) qr1.push_back({x, y});
            else if (x.first.first > mid) qr2.push_back({x, y});
            else tqr.push_back({x, y});
        }
        solve(l1, mid - 1, l2, r2, qr1), solve(mid + 1, r1, l2, r2, qr2);
        if (tqr.empty()) return;
        for (int o = l2; o <= r2; ++o) {
            for (int i = l1 - 1; i <= r1 + 1; ++i) {
                for (int j = l2 - 1; j <= r2 + 1; ++j) {
                    f[i][j] = g[i][j] = h[i][j] = -1e9;
                    tf[i][j] = tg[i][j] = th[i][j] = -1e9;
                }
            }
            h[mid][o] = th[mid][o] = 0;
            for (int i = mid; i <= r1; ++i) {
                for (int j = o; j <= r2; ++j) {
                    f[i][j] = max({f[i][j], f[i - 1][j], f[i][j - 1], g[i - 1][j], h[i][j - 1]});
                    g[i][j] = max({g[i][j], f[i][j] - ox[i], g[i][j - 1], g[i][j - 1] + ow[i][j - 1] - oy[j - 1], h[i][j - 1] + ow[i][j - 1] - ox[i]});
                    h[i][j] = max({h[i][j], f[i][j] - oy[j], h[i - 1][j], h[i - 1][j] + ow[i - 1][j] - ox[i - 1], g[i - 1][j] + ow[i - 1][j] - oy[j]});
                }
            }
            for (int i = mid; i >= l1; --i) {
                for (int j = o; j >= l2; --j) {
                    tf[i][j] = max({tf[i][j], tg[i][j] - ox[i], th[i][j] - oy[j]});
                    tf[i - 1][j] = max(tf[i - 1][j], tf[i][j]), tf[i][j - 1] = max(tf[i][j - 1], tf[i][j]);
                    tg[i - 1][j] = max(tg[i - 1][j], tf[i][j]), th[i][j - 1] = max(th[i][j - 1], tf[i][j]);
                    tg[i][j - 1] = max({tg[i][j - 1], tg[i][j], tg[i][j] + ow[i][j - 1] - oy[j - 1]});
                    tg[i - 1][j] = max(tg[i - 1][j], th[i][j] + ow[i - 1][j] - oy[j]);
                    th[i - 1][j] = max({th[i - 1][j], th[i][j], th[i][j] + ow[i - 1][j] - ox[i - 1]});
                    th[i][j - 1] = max(th[i][j - 1], tg[i][j] + ow[i][j - 1] - ox[i]);
                }
            }
            for (auto [x, y] : tqr) {
                if (x.second.first <= o && o <= x.second.second) {
                    res[y] = max(res[y], tf[x.first.first][x.second.first] + f[x.first.second][x.second.second]);
                }
            }
        }
    } else {
        int mid = (l2 + r2) >> 1;
        vector< pair< pair< pair<int, int>, pair<int, int> >, int> > qr1, qr2, tqr;
        for (auto [x, y] : qr) {
            if (x.second.second < mid) qr1.push_back({x, y});
            else if (x.second.first > mid) qr2.push_back({x, y});
            else tqr.push_back({x, y});
        }
        solve(l1, r1, l2, mid - 1, qr1), solve(l1, r1, mid + 1, r2, qr2);
        if (tqr.empty()) return;
        for (int o = l1; o <= r1; ++o) {
            for (int i = l1 - 1; i <= r1 + 1; ++i) {
                for (int j = l2 - 1; j <= r2 + 1; ++j) {
                    f[i][j] = g[i][j] = h[i][j] = -1e9;
                    tf[i][j] = tg[i][j] = th[i][j] = -1e9;
                }
            }
            g[o][mid] = tg[o][mid] = 0;
            for (int i = o; i <= r1; ++i) {
                for (int j = mid; j <= r2; ++j) {
                    f[i][j] = max({f[i][j], f[i - 1][j], f[i][j - 1], g[i - 1][j], h[i][j - 1]});
                    g[i][j] = max({g[i][j], f[i][j] - ox[i], g[i][j - 1], g[i][j - 1] + ow[i][j - 1] - oy[j - 1], h[i][j - 1] + ow[i][j - 1] - ox[i]});
                    h[i][j] = max({h[i][j], f[i][j] - oy[j], h[i - 1][j], h[i - 1][j] + ow[i - 1][j] - ox[i - 1], g[i - 1][j] + ow[i - 1][j] - oy[j]});
                }
            }
            for (int i = o; i >= l1; --i) {
                for (int j = mid; j >= l2; --j) {
                    tf[i][j] = max({tf[i][j], tg[i][j] - ox[i], th[i][j] - oy[j]});
                    tf[i - 1][j] = max(tf[i - 1][j], tf[i][j]), tf[i][j - 1] = max(tf[i][j - 1], tf[i][j]);
                    tg[i - 1][j] = max(tg[i - 1][j], tf[i][j]), th[i][j - 1] = max(th[i][j - 1], tf[i][j]);
                    tg[i][j - 1] = max({tg[i][j - 1], tg[i][j], tg[i][j] + ow[i][j - 1] - oy[j - 1]});
                    tg[i - 1][j] = max(tg[i - 1][j], th[i][j] + ow[i - 1][j] - oy[j]);
                    th[i - 1][j] = max({th[i - 1][j], th[i][j], th[i][j] + ow[i - 1][j] - ox[i - 1]});
                    th[i][j - 1] = max(th[i][j - 1], tg[i][j] + ow[i][j - 1] - ox[i]);
                }
            }
            for (auto [x, y] : tqr) {
                if (x.first.first <= o && o <= x.first.second) {
                    res[y] = max(res[y], tf[x.first.first][x.second.first] + f[x.first.second][x.second.second]);
                }
            }
        }
    }
}

signed main() {
    freopen("tower.in", "r", stdin);
    freopen("tower.out", "w", stdout);
    int n, q; scanf("%d%d", &n, &q);
    for (int i = 1; i <= n; ++i) scanf("%d", &ox[i]);
    for (int i = 1; i <= n; ++i) scanf("%d", &oy[i]);
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= n; ++j) scanf("%d", &ow[i][j]);
    }
    vector< pair< pair< pair<int, int>, pair<int, int> >, int> > qr;
    for (int i = 1; i <= q; ++i) {
        int a, b, c, d; scanf("%d%d%d%d", &a, &b, &c, &d);
        qr.push_back({{{a, b + 1}, {c, d + 1}}, i});
    }
    solve(1, n + 1, 1, n + 1, qr);
    for (int i = 1; i <= q; ++i) {
        printf("%d\n", res[i]);
    }
    return 0;
}
posted @   hhoppitree  阅读(180)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
点击右上角即可分享
微信分享提示