1.20 模拟赛口胡题解

幸亏 DP 都在前面签到,否则我又得寄

A

签到题。来点 $O(n^2)$ 正解。

$\forall k$ 做线段集为 $\{[l,r]|\sum\limits_{i=l}^ra_i=k\}$ 的线段覆盖的结果最大值。

具体地,按 $r$ 正序枚举 $[l,r]$,考虑把 $[l,r]$ 加入 $k=\sum\limits_{i=l}^ra_i$ 的贡献。

#include <cstdio>
#include <algorithm>
using namespace std;
int n, q, a[1050], R[10000050], C[10000050];
int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
        scanf("%d", a + i), a[i] += a[i - 1];
    for (int r = 1; r <= n; ++r)
        for (int l = r, v; l; --l)
            if (R[v = a[r] - a[l - 1]] < l)
                R[v] = r, q = max(q, ++C[v]);
    return !printf("%d", q);
}

($R_v$ 为 $k=v$ 的子问题当前最右端点,$C_v$ 为 $k=v$ 的子问题结果)

当然你谷神机已经无压力 1s 1e9 了,所以写个 $O(n^3)$ 大概率也是能过的(快进到 1e3 成为 $O(n^3)$ 共识

B

概率 DP 板子。

逆推:设 $f_i$ 为从 $i$ 到 $n$ 的期望,则有

$$ f_i=\dfrac{\sum\limits_{j=i+1}^{\min(i+6,n)}f_j}{\min(6,n-i)}+a_i $$

正推:设 $f_i$ 为从 $1$ 到 $i$ (不一定经过 $i$)的期望,则有

$$ f_i=\sum\limits_{j=\max(i-6,1)}^{i-1}\dfrac{f_j+a_ig_j}{\min(6,n-j)} $$

其中 $g_i$ 为到达 $i$ 的概率,且有

$$ g_i=\sum\limits_{j=\max(i-6,1)}^{i-1}\dfrac{g_j}{\min(6,n-j)} $$

觉得一堆 $\min\max$ 很烦可以把 DAG 建出来。

逆推:

#include <cstdio>
#include <algorithm>
using namespace std;
int n, a[1050];
double f[1050];
int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
        scanf("%d", a + i);
    f[n] = a[n];
    for (int i = n - 1; i; --i)
    {
        for (int j = 1; j <= min(6, n - i); ++j)
            f[i] += f[i + j];
        f[i] = f[i] / min(6, n - i) + a[i];
    }
    printf("%.7lf", f[1]);
    return 0;
}

正推:

#include <cstdio>
#include <algorithm>
using namespace std;
int n, a[1050];
double f[1050], g[1050];
int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
        scanf("%d", a + i);
    f[1] = a[1];
    g[1] = 1;
    for (int i = 2; i <= n; ++i)
        for (int j = 1; j <= min(6, i - 1); ++j)
            f[i] += (f[i - j] + a[i] * g[i - j]) / min(n - i + j, 6), g[i] += g[i - j] / min(n - i + j, 6);
    printf("%.7lf", f[n]);
    return 0;
}

C

树形 DP 板子。

设 $f_i$ 为 $i$ 子树内权值和最大的子树的权值和,则有

$$ f_u=\max\{s_u,f_v|v\in\text{son}_u\} $$

其中 $s_i$ 为 $i$ 子树权值和,且有

$$ s_u=a_u+\sum\limits_{v\in\text{son}_u}s_v $$

DP 过程中更新答案。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
struct E
{
    int v, t;
} e[200050];
int n, c, q = -2e9, s[100050], f[100050], h[100050];
void A(int u, int v)
{
    e[++c] = {v, h[u]};
    h[u] = c;
}
void D(int u, int k)
{
    for (int i = h[u], v; i; i = e[i].t)
        if ((v = e[i].v) != k)
            D(v, u), s[u] += s[v], q = max(q, f[u] + f[v]), f[u] = max(f[u], f[v]);
    f[u] = max(f[u], s[u]);
}
int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
        scanf("%d", s + i);
    for (int i = 1, u, v; i < n; ++i)
        scanf("%d%d", &u, &v), A(u, v), A(v, u);
    memset(f, 0xc0, sizeof f);
    D(1, 0);
    if (q < -1e9)
        puts("Impossible");
    else
        printf("%d", q);
    return 0;
}

D

猜结论。

结论:DAG 加若干条不定向的边一定能组成 DAG。

总的反转代价是由所有需要反转的边中,代价最高的那一条决定的,求达成目标的最小代价。

最大值最小,二分。

二分代价 $k$,则边权 $\le k$ 的边可以任意反转,若边权 $>k$ 的边组成 DAG,则此 DAG 加入边权 $\le k$ 的不定向边一定能组成 DAG,则此代价 $k$ 合法。

#include <queue>
#include <cstdio>
#include <cstring>
using namespace std;
struct S
{
    int u, v, w;
} s[100050];
struct E
{
    int v, t;
} e[100050];
queue<int> q;
int n, m, c, L, R = 1e9, M, d[100050], h[100050];
void A(int u, int v)
{
    ++d[v];
    e[++c] = {v, h[u]};
    h[u] = c;
}
bool C(int k)
{
    int r = 0;
    memset(e, 0, sizeof e);
    memset(d, 0, sizeof d);
    memset(h, c = 0, sizeof h);
    for (int i = 0; i < m; ++i)
        if (s[i].w > k)
            A(s[i].u, s[i].v);
    for (int i = 1; i <= n; ++i)
        if (!d[i])
            q.push(i);
    while (!q.empty())
    {
        int u = q.front();
        q.pop();
        ++r;
        for (int i = h[u], v; i; i = e[i].t)
            if (!--d[v = e[i].v])
                q.push(v);
    }
    return r == n;
}
int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 0; i < m; ++i)
        scanf("%d%d%d", &s[i].u, &s[i].v, &s[i].w);
    while (L <= R)
        if (C(M = L + R >> 1))
            R = M - 1;
        else
            L = M + 1;
    printf("%d", L);
    return 0;
}

E

DS。

赛时看到换根想 LCT,然后发现 LCT 不好维护子树信息,现学 ETT 换根失败,Top Tree 又不会写(

当然拿上面提到的动态树都是可以嗯做这题的,考虑容易的做法。

找规律。(在拿 DFS 序嗯造 ETT 的时候找到的,不知道 zq 什么做法

考虑线段树维护 DFS 序,发现只有换根操作不好维护。

分类讨论查询 $x$ 时 $1,x,\text{root}$ 的位置关系。

  1. $x=\text{root}$:全局最小值。
  2. $x$ 不在 $1\to\text{root}$ 路径上:以 $1$ 为根意义下 $x$ 子树最小值。
  3. $x$ 在 $1\to\text{root}$ 路径上:设 $Z$ 为 $\text{root}$ 的 $\text{dep}_{\text{root}}-\text{dep}_x-1$ 级祖先的子树,答案为 $\complement_UZ$ 的最小值。

(辅助理解第三种情况)

树上倍增维护 $k$ 级祖先,你要写长剖我不拦你。

#include <cstdio>
#include <algorithm>
#define G int m = p->s + p->t >> 1
using namespace std;
struct E
{
    int v, t;
} e[200050];
int n, m, c, g, R, a[100050], b[100050], k[100050], d[100050], s[100050], h[100050], f[100050][20];
char o;
void A(int u, int v)
{
    e[++c] = {v, h[u]};
    h[u] = c;
}
void D(int u)
{
    s[k[b[u] = ++g] = u] = 1;
    for (int i = h[u], v; i; i = e[i].t)
        if (!d[v = e[i].v])
        {
            d[v] = d[u] + 1;
            f[v][0] = u;
            for (int j = 1; f[v][j - 1]; ++j)
                f[v][j] = f[f[v][j - 1]][j - 1];
            D(v);
            s[u] += s[v];
        }
}
struct T
{
    T *l, *r;
    int s, t, v;
    T(int s, int t) : s(s), t(t) {}
    void u() { v = min(l->v, r->v); }
} * r;
void B(int s, int t, T *&p)
{
    p = new T(s, t);
    if (s == t)
        return void(p->v = a[k[s]]);
    G;
    B(s, m, p->l);
    B(m + 1, t, p->r);
    p->u();
}
void M(int l, int k, T *p)
{
    if (p->s == p->t)
        return void(p->v = k);
    G;
    if (l <= m)
        M(l, k, p->l);
    else
        M(l, k, p->r);
    p->u();
}
int Q(int l, int r, T *p)
{
    if (l <= p->s && p->t <= r)
        return p->v;
    G;
    int q = 1e9;
    if (l <= m)
        q = min(q, Q(l, r, p->l));
    if (r > m)
        q = min(q, Q(l, r, p->r));
    return q;
}
int main()
{
    scanf("%d%d", &n, &m);
    for (int i = R = 1, x; i <= n; ++i)
    {
        scanf("%d%d", &x, a + i);
        if (x)
            A(x, i), A(i, x);
    }
    D(d[1] = 1);
    B(1, n, r);
    for (int i = 0, x, y, z, q; i < m; ++i)
    {
        scanf(" %c%d", &o, &x);
        switch (o)
        {
        case 'V':
            scanf("%d", &y);
            M(b[x], y, r);
            break;
        case 'E':
            R = x;
            break;
        case 'Q':
            if (x == R)
                printf("%d\n", r->v);
            else
            {
                z = R;
                for (int k = 0; k <= 30; ++k)
                    if (d[R] - d[x] - 1 & 1 << k)
                        z = f[z][k];
                if (f[z][0] != x)
                    printf("%d\n", Q(b[x], b[x] + s[x] - 1, r));
                else
                {
                    q = Q(1, b[z] - 1, r);
                    if (b[z] + s[z] <= n)
                        q = min(q, Q(b[z] + s[z], n, r));
                    printf("%d\n", q);
                }
            }
        }
    }
    return 0;
}

F

不会。

posted @ 2023-01-21 09:16  5k_sync_closer  阅读(4)  评论(0编辑  收藏  举报  来源