【题解】 2021牛客暑期多校训练营6

A

发现随着时间的增加,凸包的每个顶点都是沿着角平分线移动。当两个相邻的顶点相交时,整个凸包的形状就会改变。

每次找到最近发生变化的顶点,可以通过距离计算出移动的时间。同样就能求出其他所有点移动距离从而求得新的凸包。

对于凸包形状不变的一段时间,面积是关于时间的一个二次函数,求出这个二次函数输出答案即可。

#include <bits/stdc++.h>
#define P pair<double,int>
#define fi first
#define se second
using namespace std;
const int N = 1e5 + 9;
const double eps = 1e-8;
int n, m;
double ans[N];
struct node {
    double x, y;
    node() {}
    node(double a, double b) {
        x = a, y = b;
    }
    node operator+(const node &a)const {
        return node(x + a.x, y + a.y);
    }
    node operator-(const node &a)const {
        return node(x - a.x, y - a.y);
    }
    double operator*(const node &a)const {
        return x * a.y - y * a.x;
    }
    node operator*(const double &a)const {
        return (node) {
            x *a, y *a
        };
    }
    node operator/(const double &a)const {
        return (node) {
            x / a, y / a
        };
    }
    double len() {
        return sqrt(x * x + y * y);
    }
} p[N];
struct Line {
    node a, b;
    Line swap() {
        return (Line) {
            b, a
        };
    }
    double getarea() {
        return a * b;
    }
    double len() {
        return (a - b).len();
    }
} L[N];
P q[N];
vector<int>v;
node getinp(Line x, Line y) {
    node a = x.a - y.a, b = x.a - x.b, c = y.b - y.a;
    double t = -(c * a) / (c * b);
    return  node(x.a.x + b.x * t, x.a.y + b.y * t);
}
Line getabd(Line l1, Line l2) {
    swap(l2.a, l2.b);
    node x = getinp(l1, l2);
    node a = x;
    node b = x + ((l1.b - l1.a) / l1.len() + (l2.b - l2.a) / l2.len());
    return (Line) {
        a, b
    };
}
double getdist(node a, Line b) {
    node c = b.b - b.a;

    if (abs(c.x) <= eps)
        return abs(a.x - b.a.x);

    double A = c.y / c.x;
    double C = b.a.y - b.a.x * A;
    double B = -1.0;
    return abs((A * a.x + B * a.y + C) / sqrt(A * A + B * B));
}
int main() {
    scanf("%d", &n);

    for (int i = 1; i <= n; ++i)
        scanf("%lf%lf", &p[i].x, &p[i].y), v.push_back(i);

    p[n + 1] = p[1];

    for (int i = 1; i <= n; ++i)
        L[i] = (Line) {
        p[i], p[i + 1]
    };

    scanf("%d", &m);

    for (int i = 1; i <= m; ++i)
        scanf("%lf", &q[i].fi), q[i].se = i;

    sort(q + 1, q + m + 1);
    int l = 1;

    while ((int)v.size() > 2) {
        int s = (int)v.size();
        int pos = 0;
        double S = 0;
        double t = 1e6;
        double A = 0, B = 0, C = 0;

        for (int i = 0; i < s; ++i) {
            Line pre = L[v[(i - 1 + s) % s]], suf = L[v[(i + 1) % s]], now = L[v[i]];
            Line now1 = (Line) {
                getinp(pre, now.swap()), getinp(now, suf.swap())
            };
            S += now1.getarea();
            Line line1 = getabd(now, pre);
            Line line2 = getabd(suf, now);
            node inp = getinp(line1, line2);
            double tt = getdist(inp, now);
            node a = now1.a - inp, b = now1.b - inp;
            double S1 = a * b / 2.0;
            A += S1;
            B += -2.0 / tt * S1;
            C += 1.0 / tt / tt * S1;

            if (tt < t)
                pos = i, t = tt;
        }

        S /= 2.0;

        for (; l <= m && (abs(q[l].fi - t) <= eps || q[l].fi <= t); ++l)
            ans[q[l].se] = S + (q[l].fi * B + q[l].fi * q[l].fi * C);

        v.erase(v.begin() + pos);
    }

    for (int i = 1; i <= m; ++i)
        printf("%.10f\n", ans[i]);

    return 0;
}

B

,把概率看成边权,直接矩阵树定理求解。时间复杂度

表示第 条边在时间 的概率,用 代替 ,每个 形如 的形式,然后带入到原来的行列式 里。

根据行列式的一些基本操作,可以把行列式 看成 ,其中 只由 组成, 只由 组成。

变换成单位矩阵,即满足 ,同时 也应该做相同变换。(因为本质同一个行列式拆分成两个),得到 。现在考虑如何求解

这个形式等价于求 的特征多项式。一般解法就是化为上海森堡矩阵在 时间内求出多项式,然后乘回 就变成答案关于 ,即 的多项式。

每一项单独等比数列求和得到答案,最终复杂度 。注意, 答案为

#include <bits/stdc++.h>
using namespace std;
const int N = 6e2 + 9;
const int p = 998244353;
int a[N][N], b[N][N];
int n, m, t, A;
int read() {
    int f = 0, x = 0;
    char ch = getchar();

    while (!isdigit(ch)) {
        f |= (ch == '-');
        ch = getchar();
    }

    while (isdigit(ch)) {
        x = x * 10 + ch - '0';
        ch = getchar();
    }

    return f ? -x : x;
}
int inv(int x, int base = p - 2) {
    int res = 1;

    while (base) {
        if (base & 1)
            res = 1ll * res * x % p;

        x = 1ll * x * x % p;
        base >>= 1;
    }

    return res;
}
void gauss(int n) {

    for (int i = 1; i <= n; i++) {

        if (a[i + 1][i] == 0) {
            bool flag = 0;

            for (int j = i + 2; j <= n; j++) {
                if (a[j][i] != 0) {
                    for (int k = i ; k <= n; k++)
                        swap(a[i + 1][k], a[j][k]);

                    for (int k = i ; k <= n; k++)
                        swap(a[k][i + 1], a[k][j]);

                    flag = 1;
                    break;
                }
            }

            if (!flag)
                continue;
        }

        int t = inv(a[i + 1][i]), tmp;

        for (int j = i + 2; j <= n; j++) {
            tmp = 1ll * t * a[j][i] % p;

            for (int k = i ; k <= n; k++)
                a[j][k] = (a[j][k] - 1ll * tmp * a[i + 1][k] % p + p) % p;

            for (int k = 1 ; k <= n; k++)
                a[k][i + 1] = (a[k][i + 1] + 1ll * tmp * a[k][j]) % p;
        }

    }

}
void poly(int n) {
    int tmp, t;
    b[0][0] = 1;

    for (int i = 1; i <= n; i++) {
        for (int j = 0; j <= n; j++) {
            if (j)
                b[i][j] = (0ll + p - b[i - 1][j - 1] + 1ll * b[i - 1][j] * a[i][i] % p) % p;
            else
                b[i][j] = 1ll * b[i - 1][j] * a[i][i] % p;
        }

        t = p - a[i][i - 1];

        for (int j = i - 1; j >= 1; j--) {
            tmp = 1ll * t % p * a[j][i] % p;

            for (int k = 0; k <= n; k++)
                b[i][k] = (b[i][k] + 1ll * tmp * b[j - 1][k]) % p;

            t = 1ll * t * (p - a[j][j - 1]) % p;
        }
    }
}
int B[N][N], K[N][N], C = 1;
void Inv(int n) {
    int tmp;

    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            K[i][j + n] = B[i][j];

    for (int i = 1; i < n; i++) {
        if (K[i][i] == 0) {
            for (int j = i + 1; j <= n; j++) {
                if (K[j][i]) {
                    swap(K[j], K[i]);
                    C = -C;
                    break;
                }
            }
        }

        C = 1ll * K[i][i] * C % p;
        tmp = inv(K[i][i]);

        for (int j = i + 1; j <= n + n; j++)
            K[i][j] = 1ll * tmp * K[i][j] % p;

        for (int k = 1; k <= n; k++) {
            if (i == k)
                continue;

            tmp = K[k][i];

            for (int j = 1; j <= n + n; j++)
                K[k][j] = (K[k][j] - 1ll * tmp * K[i][j]) % p;
        }
    }
}
int fa[N];
int get(int x) {
    return fa[x] == x ? x : fa[x] = get(fa[x]);
}
int main() {
    n = read(), m = read(), t = read(), A = read();

    if (n == 1 || m == 0) {
        puts("0");
        return 0;
    }

    for (int i = 1; i <= n; i++)
        fa[i] = i;

    for (int i = 1; i <= m; i++) {
        int x, y, a, b, P, Q;
        x = read(), y = read();
        fa[get(x)] = get(y);
        a = read(), b = read();
        P = 1ll * a * inv(b) % p;
        a = read(), b = read();
        Q = 1ll * a * inv(b) % p;
        K[x][y] = (0ll + K[x][y] + p - P + Q) % p;
        K[y][x] = (0ll + K[y][x] + p - P + Q) % p;
        K[y][y] = (0ll + K[y][y] + p + P - Q) % p;
        K[x][x] = (0ll + K[x][x] + p + P - Q) % p;
        B[x][y] = (B[x][y] + p - Q) % p;
        B[y][x] = (B[y][x] + p - Q) % p;
        B[x][x] = (B[x][x] + Q) % p;
        B[y][y] = (B[y][y] + Q) % p;
    }

    for (int i = 1; i <= n; i++)
        if (get(i) != get(1)) {
            puts("0");
            return 0;
        }

    Inv(n);

    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            a[i][j] = K[i][j + n];

    gauss(n - 1);

    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            a[i][j] = -a[i][j];

    poly(n - 1);
    int x = 1, X = 1, ans = 0;
    A = inv(A);

    for (int i = 0; i <= n; i++) {
        if (x == 1)
            X = t;
        else
            X = 1ll * (inv(x, t) - 1) * inv(x - 1) % p;

        ans = (ans + 1ll * X * b[n - 1][i]) % p;
        x = 1ll * x * A % p;
    }

    if (~n & 1)
        C = -C;

    ans = 1ll * ans * C % p;
    ans = (ans + p) % p;
    printf("%d\n", ans);
    return 0;
}

C

结论是,输出所有的 。其中设答案数量为

有递推式 ,考虑把答案分成两类:

  • ,那么有 ,和 ,其中后者方案数为
  • ,那么有 ,和 ,其中后者方案数为

所以总共增加的方案数为 种,满足递推式。相应的,就可以求出通项公式:
$$
化简即可发现满足要求。

#include <bits/stdc++.h>
using namespace std;
int n, m, x, y, z;
int main() {
    int i, j;
    cin >> n;
    int k;
    m = (n * (n - 1) / 2.0 - n) / 3.0 + 1;
    cout << m << endl;

    for (int i = 1; i <= n; i++)
        for (int j = i + 1; j <= n; j++) {
            k = n - i - j;

            if (k <= 0)
                k += n;

            if (k > j)
                printf("%d %d %d\n", i, j, k);
        }

    return 0;
}

D

为转到 的概率, 表示从 变成 的期望次数, 表示转动一次能使 变大的概率。那么根据这一次转盘是否有效能得到以下式子:
$\mathcal O(n^2)$ 的。观察转移式发现只有可能从小往大转移,是有方向的。

若枚举一个分界线 ,我们想快速用 去贡献 。因为是异或卷积的形式,可以用 加速。于是在外面套一层 分治,每次用区间右边去贡献区间左边。时间复杂度

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = (1 << 16) + 5;
const int p = 1000000007;
int inv(int x, int base = p - 2) {
    int res = 1;
    while (base) {
        if (base & 1)
            res = 1ll * res * x % p;
        x = 1ll * x * x % p;
        base >>= 1;
    }
    return res;
}
void fwt(int *a, int *b, int n, int flag = 0) {
    for (int i = 0; i < n; ++i)
        b[i] = a[i];
    for (int i = 0; (1 << i) < n; ++i) {
        int t = 1 << i;
        for (int j = 0; j < n; j += t << 1) {
            int *f = b + j, *g = b + j + t;

            for (int k = 0; k < t; ++k) {
                int s = g[k];
                g[k] = (f[k] - s + p) % p;
                f[k] = (f[k] + s) % p;
            }
        }
    }
    if (flag) {
        int iv = inv(n);
        for (int i = 0; i < n; ++i)
            b[i] = 1ll * b[i] * iv % p;
    }
}
int f[N], g[N], a[N], b[N], c[N];
void solve(int l, int r) {
    if (l == r) {
        if (g[l])
            f[l] = 1ll * (f[l] + 1) * inv(g[l]) % p;
        return;
    }
    int mid = (l + r) / 2;
    solve(mid + 1, r);
    int t = l ^ (mid + 1);
    fwt(a + t, b, mid + 1 - l);
    fwt(f + mid + 1, c, mid + 1 - l);
    for (int i = 0; i < mid + 1 - l; ++i)
        c[i] = 1ll * c[i] * b[i] % p;
    fwt(c, c, mid + 1 - l, 1);
    for (int i = l; i <= mid; ++i)
        f[i] = (f[i] + c[i - l]) % p,
               g[i] = (g[i] + b[0]) % p;
    solve(l, mid);
}
int T, n;
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >> T;
    while (T--) {
        cin >> n;
        int sum = 0;
        for (int i = 0; i < n; ++i)
            cin >> a[i], sum += a[i], f[i] = g[i] = 0;
        sum = inv(sum);
        for (int i = 0; i < n; ++i)
            a[i] = 1ll * sum * a[i] % p;
        solve(0, n - 1);
        cout << f[0] << endl;
    }
    return 0;
}

E

首先有一个操作分块的做法。把每 个操作分成一段。每次整段操作完了后,重新求出 序,对于每种颜色分别开一个数组,每次进去二分子树区间查找答案。至于散块,每次暴力 即可。时间复杂度 。实现精细的话,能卡过。

考虑另一个比较正常一点的做法,子树操作转化为序列操作。但是因为有动态加叶子,所以需要一个能动态维护子树的数据结构。直接用 ,平衡树维护括号序即可,当然也可以是

每次新插入一个叶子,让他一定是父亲的第一个儿子。这样他的管辖范围的右端点一定是下一个兄弟,或者是父亲的右端点。插在后面的话还要更改前一个兄弟,前一个兄弟的儿子.......的右端点,比较麻烦。

维护真正的 肯定是不现实的,但是只需要知道 的相对大小关系。有一个想法是:点 插入到 之间时,令 。要卡也非常容易,同一个位置一直插入,很快就寄掉了。

我们需要选择一些时候暴力重构 ,以保证正确性。于是考虑替罪羊树。因为节点数为 ,重构因子为 的树,高度不超过 ,只要保证这个值小于 即可。然后对于每一个颜色开一个平衡树,差分维护答案。时间复杂度 ,空间复杂度

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 5e5 + 9;
const double alpha = 0.7;
int T, n, m, op, u, c, ans;
struct scapegoat_tree {
    int rt, cnt, idx, id, fa, d;
    int ls[N], rs[N], sz[N], t[N], nt[N], ed[N];
    ll l[N], r[N];
    void reset() {
        rt = sz[1] = idx = 1;
        l[1] = ls[1] = rs[1] = nt[1] = ed[1] = 0;
        r[1] = l[0] = r[0] = 1e18;
    }
    void pushup(int u) {
        sz[u] = sz[ls[u]] + sz[rs[u]] + 1;
    }
    bool ok(int u) {
        return max(sz[ls[u]], sz[rs[u]]) >= sz[u] * alpha;
    }
    void dfs(int u) {
        if (ls[u])
            dfs(ls[u]);
        t[++cnt] = u;
        if (rs[u])
            dfs(rs[u]);
    }
    int build(int L, int R, ll x, ll y) {
        if (L > R)
            return 0;
        int mid = (L + R) >> 1, u = t[mid];
        ll mid2 = (x + y) >> 1;
        l[u] = x, r[u] = y;
        ls[u] = build(L, mid - 1, x, mid2);
        rs[u] = build(mid + 1, R, mid2, y);
        pushup(u);
        return u;
    }
    void rebuild(int &u) {
        cnt = 0;
        dfs(u);
        u = build(1, cnt, l[u], r[u]);
    }
    void ins_(int &u, ll x, ll y, int Fa, int D) {
        if (!u) {
            sz[u = ++idx] = 1;
            ls[u] = rs[u] = 0;
            l[u] = x, r[u] = y;
            return;
        }
        ins_(ls[u], x, (x + y) >> 1, u, 0);
        pushup(u);
        if (ok(u))
            id = u, fa = Fa, d = D;
    }
    void inss(int u, int x, int Fa, int D) {
        if (u == x) {
            ins_(rs[u], (l[u] + r[u]) >> 1, r[u], u, 1);
            pushup(u);
            if (ok(u))
                id = u, fa = Fa, d = D;
            return;
        }
        if (l[x] + r[x] < l[u] + r[u])
            inss(ls[u], x, u, 0);
        else
            inss(rs[u], x, u, 1);
        pushup(u);
        if (ok(u))
            id = u, fa = Fa, d = D;
    }
    void ins(int u) {
        id = fa = d = 0;
        inss(rt, u, 0, 0);
        if (id) {
            if (id == rt)
                rebuild(rt);
            else if (d == 0)
                rebuild(ls[fa]);
            else
                rebuild(rs[fa]);
        }
        nt[idx] = ed[idx] = nt[u];
        nt[u] = idx;
    }
} T1;
struct fhq_treap {
    int rt[N], ls[N], rs[N], sz[N], rd[N], idx, t[N], x, y, z;
    void pushup(int u) {
        sz[u] = sz[ls[u]] + sz[rs[u]] + 1;
    }
    void split(int u, int x, int &l, int &r) {
        if (!u) {
            l = r = 0;
            return;
        }
        if (T1.l[u] + T1.r[u] < T1.l[x] + T1.r[x])
            l = u, split(rs[u], x, rs[u], r);
        else
            r = u, split(ls[u], x, l, ls[u]);
        pushup(u);
    }
    int merge(int u, int v) {
        if (!u || !v)
            return u ^ v;
        if (rd[u] < rd[v])
            rs[u] = merge(rs[u], v);
        else
            ls[v] = merge(u, ls[v]), u = v;
        pushup(u);
        return u;
    }
    void ins(int c) {
        t[++idx] = c;
        ls[idx] = rs[idx] = 0;
        sz[idx] = 1;
        rd[idx] = rand();
        split(rt[c], idx, x, y);
        rt[c] = merge(merge(x, idx), y);
    }
    int query(int c, int u) {
        split(rt[c], u, x, y);
        split(y, T1.ed[u], y, z);
        int res = sz[y];
        rt[c] = merge(merge(x, y), z);
        return res;
    }
    void clear() {
        for (int i = 1; i <= idx; ++i)
            rt[t[i]] = 0;
        idx = 0;
    }
} T2;
int main() {
    srand(114514123);
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d", &c, &m);
        T1.reset(), T2.ins(c);
        ans = 0;
        while (m--) {
            scanf("%d%d%d", &op, &u, &c);
            op ^= ans, u ^= ans, c ^= ans;
            if (op == 1)
                T1.ins(u), T2.ins(c);
            else
                printf("%d\n", ans = T2.query(c, u));
        }
        T2.clear();
    }
    return 0;
}

F

有一个下界就是,总时间除以盘子数量上取整。设这个时间为

的时候, 把所有牛排尽可能多地塞进第一个盘子里,如果有一块塞不下了,就把它拆成两份,然后继续往第二个盘子里塞。所有烤盘按照塞进去的顺序开始烤就能到达这个下界。

的时候,显然是无法满足烤的时间最大的那块肉的,因为分在不同的烤盘里,时间会有重复的部分。一块牛排肯定不能在一个时间出现在两个烤盘上。只需要将时间调整为 即可。

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 9;
int a[N], cnt = 1, lim = 0;
int n, m;
signed main() {
    scanf("%d%d", &n, &m);
    int sum = 0, t = 0;
    for (int i = 1; i <= n; i++) {
        scanf("%lld", &a[i]);
        t = max(t, a[i]);
        sum += a[i];
    }
    t = max(t, (sum - 1) / m + 1);
    for (int i = 1; i <= n; i++) {
        if (t - lim < a[i]) {
            printf("2 %lld 0 %lld %lld %lld %lld\n", cnt + 1, a[i] - (t - lim), cnt, lim, t);
            cnt++;
            lim = lim + a[i] - t;
        } else {
            printf("1 %lld %lld %lld\n", cnt, lim, lim + a[i]);
            lim += a[i];
            if (lim == t)
                cnt++, lim = 0;
        }
    }return 0;
}

G

的边数为 ,分解

枚举一个质因子 ,统计两个数 并且 的方案数。

发现所有合法的 只需要满足 含有质数 的个数小于

也就是 个( 表示 的因子个数)。但是每次枚举所有的 不方便优化。不妨观察删掉上述 边构成的图。

发现居然是 个一模一样的图!那么 就有一个快速的计算方法,那就是枚举任意一个质因子
$\rm min25$ 优化。

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 9;
const int p = 1145140019;
int read() {
    int f = 0, x = 0;
    char ch = getchar();
    while (!isdigit(ch)) {
        f |= (ch == '-');
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return f ? -x : x;
}
int n, f[N], g[N], d[N], w[N];
int T, pr[N], len;
bool vis[N];
void init(int n = 1e5) {
    for (int i = 2; i <= n; ++i) {
        if (!vis[i])
            pr[++len] = i, vis[i] = 1;
        for (int j = 1; j <= len; ++j) {
            if (pr[j] > n / i)
                break;
            vis[pr[j]*i] = 1;
            if (i % pr[j] == 0)
                break;
        }
    }
}
int cal() {
    int cnt = 0, t = sqrt(n), ml = 0;
    for (int i = 1; i <= n; i = n / (n / i) + 1)
        w[++cnt] = n / i;
    reverse(w + 1, w + cnt + 1);
    for (int i = 1; i <= cnt; ++i)
        g[i] = w[i] - 1;
    for (int i = 1; i <= len; ++i, ++ml) {
        int P = pr[i];
        if (P > t)
            break;
        for (int j = cnt; j; --j) {
            int l = w[j] / P;
            if (l < P)
                break;
            l = l <= t ? l : cnt - n / l + 1;
            g[j] = (g[j] + p - g[l] + i - 1) % p;
        }
    }
    for (int i = 1; i <= cnt; ++i)
        f[i] = g[i], d[i] = (2 * f[i]) % p;
    for (int i = ml; i >= 1; --i) {
        int P = pr[i];
        for (int j = cnt; j; --j) {
            int l = w[j] / P;
            if (l < P)
                break;
            for (int c = 1; l >= P; ++c, l /= P) {
                int pos = l <= t ? l : cnt - n / l + 1;
                d[j] = (d[j] + (c + 1) * (d[pos] + p - 2LL * i) % p + c + 2) % p,
                       f[j] = (f[j] + (c + 1) * (f[pos] + p - i) % p + c * (d[pos] + p - 2ll * i) % p + c + 1) % p;
            }
        }
    }
    return f[cnt];
}
signed main() {
    T = read();
    init();
    while (T--) {
        n = read();
        printf("%lld\n", cal());
    }
    return 0;
}

H

若把互相能到达的点看成同一个点,那么不同的点只有 个。于是将整个坐标系用 大小的矩形分割开来,然后将所有矩阵重合于以 为左下角,大小为 的矩阵。若存在一个点未被覆盖,那么他就是合法答案。

一个输入的矩形最多会被分成 部分,暴力拆解,然后用扫描线做一遍矩形并即可。按照 坐标扫,维护区间最小值是否为 。时间复杂度

#include <bits/stdc++.h>
#define y1 qweqdfrgedt
using namespace std;
const int N = 1e5 + 9;
int read() {
    int f = 0, x = 0;
    char ch = getchar();
    while (!isdigit(ch)) {
        f |= (ch == '-');
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return f ? -x : x;
}
struct node {
    int L, R;
};
struct Seg {
#define ls p<<1
#define rs p<<1|1
    int mn[N << 2], tag[N << 2], L, R, W;
    void push_up(int p) {
        mn[p] = min(mn[ls], mn[rs]);
    }
    void push_down(int p) {
        if (tag[p]) {
            tag[ls] += tag[p], mn[ls] += tag[p];
            tag[rs] += tag[p], mn[rs] += tag[p];
            tag[p] = 0;
        }
    }
    void change(int p, int l, int r) {
        if (l >= L && r <= R) {
            mn[p] += W, tag[p] += W;
            return;
        }
        push_down(p);
        int mid = (l + r) >> 1;
        if (L <= mid)
            change(ls, l, mid);
        if (R > mid)
            change(rs, mid + 1, r);
        push_up(p);
    }
    int get(int p, int l, int r) {
        if (l == r)
            return l;
        int mid = (l + r) >> 1;
        push_down(p);
        if (!mn[ls])
            return get(ls, l, mid);
        else
            return get(rs, mid + 1, r);
    }
    int query1(int n) {
        if (mn[1])
            return -1;
        return get(1, 1, n) - 1;
    }
} T;
vector<node> Add[N], Del[N];
int n, d;
void get(int &x) {
    x = (x % d + d) % d;
}
void push(int x1, int x2, int y1, int y2) {
    if (x1 >= x2 || y1 >= y2)
        return;
    Add[x1].push_back(node{y1 + 1, y2});
    Del[x2].push_back(node{y1 + 1, y2});
}
void add(int x1, int x2, int y1, int y2) {
    if (y2 - y1 >= d) {
        push(x1, x2, 0, d);
        return;
    }
    get(y1), get(y2);
    if (y1 > y2) {
        push(x1, x2, y1, d), push(x1, x2, 0, y2);
    } else
        push(x1, x2, y1, y2);
}
signed main() {
    int x1, y1, x2, y2;
    n = read(), d = read();
    for (int i = 0; i < n; i++) {
        x1 = read(), y1 = read(), x2 = read(), y2 = read();
        if (x2 - x1 >= d) {
            add(0, d, y1, y2);
            continue;
        }
        get(x1), get(x2);
        if (x1 > x2) {
            add(x1, d, y1, y2), add(0, x2, y1, y2);
        } else
            add(x1, x2, y1, y2);
    }
    for (int i = 0; i < d; i++) {
        for (auto x : Add[i])
            T.L = x.L, T.R = x.R, T.W = 1, T.change(1, 1, d);
        for (auto x : Del[i])
            T.L = x.L, T.R = x.R, T.W = -1, T.change(1, 1, d);
        int y = T.query1(d);
        if (y != -1) {
            printf("YES\n%d %d\n", i, y);
            return 0;
        }
    }
    puts("NO");
    return 0;
}

I

因为有区间并的补等于区间补的交,所以直接输出每一段未被区间覆盖的区间的补即可。

#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
pair<int, int> p[N];
int main() {
    int t, n, m, l, r;
    cin >> t;
    while (t--) {
        cin >> n >> m;
        for (int i = 0; i < m; i++) {
            cin >> p[i].first >> p[i].second;
        }
        cout << m << endl;
        sort(p, p + m);
        for (int i = 0; i < m; i++) {
            cout << p[i].first << " " << p[(m + i - 1) % m].second << endl;
        }
    }
    return 0;
}

J

对于一个大小为偶数的连通块是不会操作的。

对于一个大小为奇数的连通块,想贪心只删去一个点。当这个点不是割点时,显然是可行的。否则,需要考虑删去这个点后剩下的每个连通块的答案。

显然,对于一个连通块不会同时分离出割点和非割点。否则只分离非割点是更优的选择。

考虑任意一种删去割点的方案,需要满足若删去这些被选择的割点,剩下的每个连通块大小必须是偶数(不然就不止删这些点),从而得到,选择的割点个数一定是奇数(奇+偶=奇)。

并且当选择割点数大于等于 的时候,总能选择出两个割点,满足它们之间存在一条路径不经过其他割点。同时不选择它们,会发现仍然满足剩下的每个连通块大小是偶数的条件,并且答案更优了!

所以对于一个大小为奇数的连通块,若删去割点,只会删去一个。那枚举所有割点,看删掉后剩下连通块大小是否都是偶数,然后和每个非割点的答案取最优即可。时间复杂度

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e6 + 9;
int n, m, T, mn, idx;
int dfn[N], low[N], a[N], sz[N];
vector<int>e[N];
void chmn(int &x, int y) {
    (x > y) &&(x = y);
}
int read() {
    int f = 0, x = 0;
    char ch = getchar();
    while (!isdigit(ch)) {
        f |= (ch == '-');
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return f ? -x : x;
}
void dfs(int x, int fa) {
    sz[x] = 1;
    int flag = 1;
    dfn[x] = low[x] = ++idx;
    for (int to : e[x]) {
        if (to != fa)
            if (!dfn[to]) {
                dfs(to, x);
                low[x] = min(low[x], low[to]);
                sz[x] += sz[to];
                if (low[to] >= dfn[x] && (sz[to] & 1))
                    flag = 0;
            } else
                chmn(low[x], dfn[to]);
    }
    if (flag)
        chmn(mn, a[x]);
}
signed main() {
    T = read();
    while (T--) {
        n = read(), m = read();
        int sum = 0;
        mn = 1e18;
        for (int i = 1; i <= n; i++) {
            a[i] = read();
            sum += a[i];
            e[i].clear();
            dfn[i] = 0;
        }
        for (int i = 1; i <= m; i++) {
            int x, y;
            x = read(), y = read();
            e[x].push_back(y);
            e[y].push_back(x);
        }
        if (n % 2 == 0)
            printf("%lld\n", sum);
        else {
            idx = 0;
            dfs(1, 0);
            printf("%lld\n", sum - mn * 2);
        }
    }
}

K

首先,不考虑数据范围,有如下几个做法:

暴力树剖,维护区间左右端点选不选然后合并,时间复杂度 .

倍增,维护 往上跳 的答案。时间复杂度都是 。空间复杂度

但是考虑这题数据范围,需要一个 回答询问的算法。

对于序列上的问题,用猫树在每个区间维护所有点到中点的答案,查询时找到 ,即可 回答询问。

发现猫树的思想是,每次选择中点,然后递归下去,类似于 分治。那相应的,在树上就选择重心,然后递归处理子树,像点分治那样。这样询问也是 的。预处理时空复杂度都是

具体实现时需要 LCA 。

#include <bits/stdc++.h>
#define ll long long
#define PII pair<int,int>
using namespace std;
const int P = 998244353;
const int N = 5e5 + 9;
int max(int x, int y) {
    return x > y ? x : y;
}
struct Rand {
    unsigned int n, seed;
    Rand(unsigned int n, unsigned int seed)
        : n(n), seed(seed) {}
    int get(long long lastans) {
        seed ^= seed << 13;
        seed ^= seed >> 17;
        seed ^= seed << 5;
        return (seed ^ lastans) % n + 1;
    }
};
int n, a[N], sz[N];
vector<int> e[N];
bool vis[N];
void getSize(int x, int p) {
    sz[x] = 1;
    for (int y : e[x]) {
        if (y == p || vis[y])
            continue;
        getSize(y, x);
        sz[x] += sz[y];
    }
}
int getrt(int x, int p, int s) {
    for (int y : e[x]) {
        if (y == p || vis[y] || sz[y] * 2 < s)
            continue;
        return getrt(y, x, s);
    }
    return x;
}
ll f[20][N][2], dp[N][2][2];
int fa[20][N];
void dfs(int x, int p, int d, int r) {
    fa[d][x] = r;
    f[d][x][0] = max(dp[x][0][0], dp[x][0][1]);
    f[d][x][1] = max(dp[x][1][0], dp[x][1][1]);
    for (int y : e[x]) {
        if (y == p || vis[y])
            continue;
        dp[y][0][0] = max(dp[x][0][0], dp[x][0][1]);
        dp[y][0][1] = dp[x][0][0] + a[y];
        dp[y][1][0] = max(dp[x][1][0], dp[x][1][1]);
        dp[y][1][1] = dp[x][1][0] + a[y];
        dfs(y, x, d, r);
    }
}
void solve(int x, int d) {
    getSize(x, 0);
    x = getrt(x, 0, sz[x]);
    dp[x][0][0] = 0;
    dp[x][0][1] = dp[x][1][0] = -1e18;
    dp[x][1][1] = a[x];
    dfs(x, 0, d, x);
    vis[x] = true;
    for (int y : e[x]) {
        if (vis[y])
            continue;
        solve(y, d + 1);
    }
}
ll calc(int x, int y) {
    if (x == y)
        return a[x];
    for (int i = 0; i < 20; i++) {
        if (fa[i + 1][x] != fa[i + 1][y]) {
            int p = fa[i][x];
            return max(f[i][x][1] + f[i][y][1] - a[p], f[i][x][0] + f[i][y][0]);
        }
    }return 0;
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, m, seed;
    cin >> n >> m >> seed;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    for (int i = 2, x; i <= n; i++) {
        cin >> x;
        e[x].push_back(i);
        e[i].push_back(x);
    }
    solve(1, 0);
    ll lastans = 0, ans = 0;
    Rand rand(n, seed);
    for (int i = 0; i < m; i++) {
        int u = rand.get(lastans);
        int v = rand.get(lastans);
        int x = rand.get(lastans);
        lastans = calc(u, v);
        ans = (ans + lastans % P * x) % P;
    }
    cout << ans << "\n";
    return 0;
}
全部评论

相关推荐

点赞 收藏 评论
分享
牛客网
牛客企业服务