Y3OI Summer Round #5 题解

前言

《关于出题人不知道比赛已经比完了这档事》

题解

以下是各题的题解。

Y3OI Summer Round #5 A. 完美的字符串

对于 \(10\%\) 的数据,签到送分。

对于 \(30\%\) 的数据,枚举 \(p_i\),再枚举 \(a_i\),判断可以用 KMP 算法,也可以不用,时间复杂度 \(O(n^2\log n)\)

对于 \(100\%\) 的数据,考虑 KMP 算法中 \(f\) 数组的性质:对于每个 \(i\) 而言,长度为 \(f[i]\) 的前缀与长度为 \(f[i]\) 的后缀是相等的。

由题意可知,如果 \(i\) 有一个公共的前后缀,其长度为 \(l\),那么这个前缀 \(i\) 就有一个周期为 \(i-l\)。于是我们用上失配数组 \(f\) 求解即可。

但是,本题求的是最大的周期,因此我们应该求出最小的 \(l\) 满足题意。当然,这里可以用“记忆化”的思想来简化时间复杂度。

这样,时间复杂度为 \(O(n)\)

#include <bits/stdc++.h>
#define Re register
using namespace std;

typedef long long ll;

const int N = 10000005;

char s[N];
int n, f[N];
ll ans;

int main() {
    scanf("%d", &n);
    scanf("%s", s + 1);
    for (Re int i = 2, j = 0; i <= n; i++) {
        while (j > 0 && s[i] != s[j + 1]) j = f[j];
        if (s[i] == s[j + 1])
            j++;
        f[i] = j;
    }
    for (Re int i = 1; i <= n; i++) {
        int j = i;
        while (f[j] > 0) j = f[j];
        if (f[i] > 0)
            f[i] = j;
        ans += i - j;
    }
    printf("%lld", ans);
    return 0;
}

Y3OI Summer Round #5 B. 收割庄稼

对于 \(20\%\) 的数据,暴力跑一遍即可。

对于 \(100\%\) 的数据,考虑上一棵线段树。(这应该挺好想的,不过为什么没有人写呢?)

我们发现,庄稼的高度始终是单调不减的。

也就是说,我们将生长速度 \(a_i\) 从小到大排个序,就可以得到庄稼的高度的关系:\(l_i\leq l_j,(i\leq j)\)

于是对于每个询问,我们只要找到被割的庄稼的左端点,进行区间修改,用线段树维护区间和再减去修改后的区间和就可以了。

时间复杂度 \(O(n\log n)\)

#include <bits/stdc++.h>
#define Re register
using namespace std;

typedef long long ll;

const int N = 500005;

struct SegTree {
    ll l, r, sum, mx, lzy, day;
} T[N << 2];

int n, m;
ll a[N], sum[N], d, b;

inline void build(int p, int l, int r) {
    T[p].lzy = -1, T[p].l = l, T[p].r = r;
    if (l == r)
        return;
    int mid = (l + r) >> 1;
    build(p << 1, l, mid), build(p << 1 | 1, mid + 1, r);
}

inline void pushup(int p) {
    T[p].sum = T[p << 1].sum + T[p << 1 | 1].sum;
    T[p].mx = T[p << 1 | 1].mx;
}

inline void update1(int p, int l, int r, ll val) {
    T[p].day += val;
    T[p].sum += 1ll * (sum[r] - sum[l - 1]) * val;
    T[p].mx += 1ll * a[r] * val;
}

inline void update2(int p, int l, int r, ll val) {
    T[p].lzy = T[p].mx = val;
    T[p].sum = 1ll * (r - l + 1) * val;
    T[p].day = 0;
}

inline void pushdown(int p) {
    int mid = (T[p].l + T[p].r) >> 1, l = T[p].l, r = T[p].r;
    if (T[p].lzy != -1) {
        update2(p << 1, l, mid, T[p].lzy);
        update2(p << 1 | 1, mid + 1, r, T[p].lzy);
        T[p].lzy = -1;
    }
    if (T[p].day) {
        update1(p << 1, l, mid, T[p].day);
        update1(p << 1 | 1, mid + 1, r, T[p].day);
        T[p].day = 0;
    }
}

inline int query1(int p, ll v) {
    if (T[p].sum < v)
        return -1;
    if (T[p].l == T[p].r)
        return T[p].l;
    pushdown(p);
    if (T[p << 1].mx >= v)
        return query1(p << 1, v);
    return query1(p << 1 | 1, v);
}

inline ll query2(int p, int l, int r, ll v) {
    ll ret = 0;
    if (l > r)
        return 0;
    if (l <= T[p].l && T[p].r <= r) {
        ll tmp = T[p].sum;
        update2(p, T[p].l, T[p].r, v);
        return tmp - T[p].sum;
    }
    int mid = (T[p].l + T[p].r) >> 1;
    if (l <= mid)
        ret += query2(p << 1, l, r, v);
    if (mid < r)
        ret += query2(p << 1 | 1, l, r, v);
    pushup(p);
    return ret;
}

int main() {
    scanf("%d%d", &n, &m);
    for (Re int i = 1; i <= n; i++) {
        scanf("%lld", &a[i]);
    }
    sort(a + 1, a + n + 1);
    for (Re int i = 1; i <= n; i++) {
        sum[i] = sum[i - 1] + a[i];
    }
    build(1, 1, n);
    ll lst = 0;
    while (m--) {
        scanf("%lld%lld", &d, &b);
        update1(1, 1, n, d - lst);
        lst = d;
        ll L = query1(1, b);
        if (L == -1)
            printf("0\n");
        else
            printf("%lld\n", query2(1, L, n, b));
    }
    return 0;
}

Y3OI Summer Round #5 C. 刷题

对于 \(5\%\) 的数据,直接 dfs 即可。

对于 \(15\%\) 的数据,本来是给 \(30\%\) 那一档的大常数选手分,结果有些人玄学做法搞过了(

对于 \(30\%\) 的数据,考虑网络流板子套上,建边如下:

  • \(S\to i,f=a_i\)

  • \(i\to T',f=b_i\)

  • \(i\to i+1,f=∞\)

  • \(T'\to T,f=k\)

时间复杂度 \(O(n^3)\),常数好可以过。

对于另外 \(20\%\) 的数据,没啥用,就放那,算是个比较整齐的数据(

对于另外 \(10\%\) 的数据,显然 \(a\) 取前 \(k\) 个,\(b\) 取后 \(k\) 个。

对于 \(100\%\) 的数据,用 wqs 二分或线段树模拟费用流的过程即可。(因为本题费用流模型并不复杂)

时间复杂度 \(O(n\log n)\)\(O(n\log^2 n)\)

#include <bits/stdc++.h>
#define Re register
using namespace std;

typedef long long ll;
const int N = 500005;
int n, k;
ll a[N], b[N];
priority_queue<pair<ll, ll> > q;

int main() {
    scanf("%d%d", &n, &k);
    for (Re int i = 1; i <= n; i++) {
        scanf("%lld", &a[i]);
    }
    for (Re int i = 1; i <= n; i++) {
        scanf("%lld", &b[i]);
    }
    ll l = 0, r = 2000000009;
    while (l <= r) {
        ll mid = (l + r) >> 1;
        int cnt = 0;
        ll tot = 0;
        for (Re int i = 1; i <= n; i++) {
            q.push(make_pair(mid - a[i], 0));
            ll val = b[i] - q.top().first;
            if (val < 0) {
                tot += val;
                q.pop();
                q.push(make_pair(b[i], 1));
            }
        }
        while (!q.empty()) cnt += q.top().second, q.pop();
        if (cnt == k) {
            printf("%lld", tot + 1ll * k * mid);
            return 0;
        }
        if (cnt < k)
            l = mid + 1;
        if (cnt > k)
            r = mid - 1;
    }
    return 0;
}

Y3OI Summer Round #5 D. 最短路

对于 \(10\%\) 的数据,随便跑即可。

对于 \(40\%\) 的数据,考虑 Dijkstra 算法,发现每个点可以更新的点的最短路都是 \(dis[i]+a[i]\)

故维护一个堆,按这个排序,可以保证每个节点只被更新一次。

时间复杂度 \(O(n^2\log n)\)

对于另外 \(20\%\) 的数据,就是一个裸的 Dijkstra 算法。

对于 \(100\%\) 的数据,考虑如何快速找出所有没有更新的点。

用点分树!

从每个重心开始 bfs,然后用队列记录下所遍历到的每个点,这显然总共只有 \(n\log n\) 个节点。

查找没修改过的点,对于子树内的点,可以直接删队列内的点,对于子树外的点,可以找点分树父亲的队列进行修改。

时间复杂度 \(O(n\log n)\)

#include <bits/stdc++.h>
#define Re register
using namespace std;

typedef long long ll;
const int N = 200005;

struct Edge {
    int ver, nxt;
} e[N << 1];

int n, S, cnt, T;
int hd[N], d[N], dfn[N], pos[N], dep[N];
bool vis[N];
int lg[N << 1], st[N << 1][21];
ll dis[N], a[N];

struct cmp {
    bool operator()(int x, int y) const { return dis[x] + a[x] > dis[y] + a[y]; }
};
priority_queue<int, vector<int>, cmp> q;

inline void add(int u, int v) {
    cnt++;
    e[cnt] = (Edge){ v, hd[u] };
    hd[u] = cnt;
}

inline void dfs(int u, int f) {
    dep[u] = dep[f] + 1;
    pos[u] = ++T;
    st[T][0] = u;
    for (Re int i = hd[u]; i; i = e[i].nxt) {
        int v = e[i].ver;
        if (v == f)
            continue;
        dfs(v, u);
        st[++T][0] = u;
    }
}

inline int lca(int x, int y) {
    x = pos[x], y = pos[y];
    if (x > y)
        swap(x, y);
    int t = lg[y - x + 1];
    return dep[st[x][t]] < dep[st[y - (1 << t) + 1][t]] ? st[x][t] : st[y - (1 << t) + 1][t];
}

inline int Dis(int x, int y) { return dep[x] + dep[y] - dep[lca(x, y)] * 2; }

int sz, rt, mx;
int sze[N], F[N], mrk[N], in[N];

inline void getroot(int x, int f) {
    sze[x] = 1;
    int son = 0;
    for (Re int i = hd[x]; i; i = e[i].nxt) {
        int v = e[i].ver;
        if (v == f || vis[v])
            continue;
        getroot(v, x);
        sze[x] += sze[v];
        son = max(son, sze[v]);
    }
    son = max(son, sz - sze[x]);
    if (son < mx)
        mx = son, rt = x;
}

queue<pair<int, int> > Q;
int h[N], nxt[N * 30];
int Cnt, tp;
pair<int, int> son[N * 30], s[N];

inline void Add(int u, pair<int, int> now) {
    Cnt++;
    son[Cnt] = now;
    nxt[Cnt] = h[u];
    h[u] = Cnt;
}

inline void bfs(int x) {
    Q.push(make_pair(0, x));
    in[x] = x;
    while (!Q.empty()) {
        pair<int, int> now = Q.front();
        Q.pop();
        s[++tp] = now;
        int u = now.second, dep = now.first;
        for (Re int i = hd[u]; i; i = e[i].nxt) {
            int v = e[i].ver;
            if (in[v] == x || vis[v])
                continue;
            in[v] = x;
            Q.push(make_pair(dep + 1, v));
        }
    }
    while (tp) Add(x, s[tp--]);
}

inline void div(int x) {
    vis[x] = 1;
    for (Re int i = hd[x]; i; i = e[i].nxt)
        if (!vis[e[i].ver])
            bfs(x);
    for (Re int i = hd[x]; i; i = e[i].nxt) {
        int v = e[i].ver;
        if (vis[v])
            continue;
        sz = sze[v];
        mx = 1e9;
        getroot(v, x);
        F[rt] = x;
        div(rt);
    }
}

inline void del(int x, int dit, ll v, int u) {
    if (d < 0)
        return;
    while (h[x] && son[h[x]].first <= dit) {
        int k = son[h[x]].second;
        h[x] = nxt[h[x]];
        if (mrk[k])
            continue;
        dis[k] = v, mrk[k] = 1, q.push(k);
    }
    if (F[x])
        del(F[x], d[u] - Dis(u, F[x]), v, u);
}

int main() {
    scanf("%d", &n);
    for (Re int i = 1; i < n; i++) {
        int u, v;
        scanf("%d%d", &u, &v);
        add(u, v), add(v, u);
    }
    for (Re int i = 1; i <= n; i++) {
        scanf("%d", &d[i]);
    }
    for (Re int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
    }
    dfs(1, 0);
    for (Re int i = 2; i <= T; i++) lg[i] = lg[i >> 1] + 1;
    for (Re int i = 1; i <= 19; i++)
        for (Re int j = 1; j + (1 << i) <= T; j++)
            st[j][i] = dep[st[j][i - 1]] < dep[st[j + (1 << i - 1)][i - 1]] ? st[j][i - 1]
                                                                            : st[j + (1 << i - 1)][i - 1];
    sz = n;
    mx = 1e9;
    getroot(1, 0);
    div(rt);
    dis[1] = 0;
    mrk[1] = 1;
    q.push(1);
    while (!q.empty()) {
        int u = q.top();
        q.pop();
        del(u, d[u], dis[u] + a[u], u);
    }
    for (Re int i = 1; i <= n; i++) printf("%lld ", dis[i]);
    return 0;
}
posted @ 2021-08-21 14:05  kebingyi  阅读(62)  评论(0编辑  收藏  举报