2025 暑期 mx 集训 7.17

T1

https://www.mxoj.net/problem/P60404?contestId=35

题意

你有 \(n\) 个点,有 \(26\) 种颜色,从浅到深为 AZ

你可以给一段连续的区间上色,深颜色会覆盖浅颜色。

给你 \(n\) 个点的目标颜色。

每次询问 \([l, r]\),求涂 \([1, l)\)\((r, n]\) 的区间最少需要上多少次色。

\(n,q \leq 10^5\)

Solution

考虑这两段无关,所以分开计算。

考虑合并贡献,比如:x,...,x

那么如果这两个 x 之间,没有比他更小的点了,那么就可以一次涂完这两个。

所以我们分别维护一个前缀和后缀即可。

具体可以维护每个颜色上一次出现的位置,然后查询是否有 \(<\) 当前颜色的上一次出现位置 \(\ge\) 我这个颜色的上一次出现位置。

因为只有 \(26\) 种,所以可以暴力。当然也可以线段树。

Code

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10, inf = 0x3f3f3f3f;

int n, q, pre[N], suf[N], p[N];
char a[N];

int main()
{
    cin.tie(0)->ios::sync_with_stdio(false);
    cin >> n >> q;
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 1; i <= n; i++) {
        pre[i] = pre[i - 1];
        if (!p[a[i]]) pre[i]++;
        else {
            bool ok = 0;
            for (int j = 'A'; j < a[i]; j++) if (p[j] >= p[a[i]]) ok = 1;
            pre[i] += ok;
        }
        p[a[i]] = i;
    }
    for (int i = 'A'; i <= 'Z'; i++) p[i] = n + 1;
    for (int i = n; i >= 1; i--) {
        suf[i] = suf[i + 1];
        if (p[a[i]] == n + 1) suf[i]++;
        else {
            bool ok = 0;
            for (int j = 'A'; j < a[i]; j++) if (p[j] <= p[a[i]]) ok = 1;
            suf[i] += ok;
        }
        p[a[i]] = i;
    }
    for (int i = 1; i <= q; i++) {
        int l, r; cin >> l >> r;
        cout << pre[l - 1] + suf[r + 1] << "\n";
    }
    return 0;
}

T2

https://www.mxoj.net/problem/P60447

题意

给你一个点,然后 \(q\) 次操作。

  • 添加一个点,他的父亲为 \(fa\),权值为 \(w\)
  • 查询 \(\sum_{u = 1}^n \sum_{v = 1}^u w_{lca(u,v)}\)

\(q \leq 10^5\)

Solution

首先考虑先把树离线下来。

然后我们可以考虑每个点作为 \(lca\) 的次数。

那么有 \(\sum_{i = 1}^n w_i \times (sz_i^2 - \sum_{v\in son_i} sz_v^2)\)

因为这题要保证 \(v \leq u\) 所以其实应该是 \(\frac{sz(sz + 1)}{2}\) 但是这样写太麻烦了,所以就简写成上面的形式。

然后这个式子可以用线段树维护,然后动态修改。巨麻烦。

所以有一个比较简单的,维护增量的做法。

考虑新增一个点,那么只有他到根的路径上这些点会作为 \(lca\)

然后考虑每个点新的贡献是:\(w_u \times (sz_u - sz_v)\) 其中 \(v\)\(u\) 的儿子且包含新增的点。

我们把新增的点叫 \(n\),那么除了 \(n\) 之外的点对的贡献我们都算过了,现在只算与 \(n\) 有关的贡献。

然后这个 \(u\) 的子树内,\(n\) 所在的子树不能选,因为这样他和 \(n\)\(lca\) 就不是 \(u\) 了。

然后还有一个非常牛的东西,我们把每个点的权值变成 \(w_u - w_{fa}\)

然后假如说:\(u, fa\) 本来原先的贡献是:\(w_u \times (sz_u - sz_v) + w_{fa} \times (sz_{fa} - sz_{u})\)

然后把换成新贡献:\((w_u - w_{fa}) \times (sz_u - sz_v) + (w_{fa} - w_{fa_{fa}}) \times (sz_{fa} - sz_u)\)

新贡献里 \(sz_v,sz_u\) 这里多减了,把他们去掉。

\[(w_u - w_{fa})\times sz_u + (w_{fa} - w_{fa_{fa}}) \times sz_{fa} \]

整体的全加起来,发现:对于原来的贡献,我们有一个 \(w_u \times (-sz_v)\),现在我们这里恰好全部抵消掉了。

于是只需要维护 \(c_u \times sz_u\) 的和即可。

感性理解就是,原来所有父亲都要减掉一部分儿子的贡献,现在在儿子那里直接加上一个负贡献直接抵消掉父亲那里的减。

Code

#include <bits/stdc++.h>

using namespace std;

#define ll long long

const int N = 1e5 + 10, inf = 0x3f3f3f3f;

int t, n, a[N], c[N];
int dep[N], sz[N], son[N], f[N], top[N], dfn[N], rnk[N], cnt;
struct qry { int opt, u; } q[N];
vector<int> e[N];

void dfs(int u, int fa)
{
    c[u] = a[u] - a[fa];
    dep[u] = dep[(f[u] = fa)] + 1; sz[u] = 1;
    for (auto v : e[u]) {
        if (v == fa) continue;
        dfs(v, u);
        sz[u] += sz[v];
        if (sz[son[u]] < sz[v]) son[u] = v;
    }
}

void dfs1(int u, int t)
{
    top[u] = t;
    dfn[u] = ++cnt, rnk[cnt] = u;
    if (son[u]) dfs1(son[u], t);
    for (auto v : e[u]) if (v != f[u] && v != son[u]) dfs1(v, v);
}

struct node {
    ll ans, tag;
    ll sum;
} tr[N << 2];

#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define mid (l + r) / 2

void build(int rt, int l, int r)
{
    if (l == r) return tr[rt].sum = c[rnk[l]], void();
    build(lson, l, mid), build(rson, mid + 1, r);
    tr[rt].sum = tr[lson].sum + tr[rson].sum;
}

void push_down(int rt)
{
    ll &u = tr[rt].tag;
    if (!u) return;
    tr[lson].ans += u * tr[lson].sum;
    tr[rson].ans += u * tr[rson].sum;
    tr[lson].tag += u, tr[rson].tag += u;
    u = 0;
}

void update(int rt, int l, int r, int sp, int ep, int v)
{
    if (sp <= l && r <= ep) {
        tr[rt].ans += v * tr[rt].sum;
        tr[rt].tag += v;
        return;
    }
    push_down(rt);
    if (sp <= mid) update(lson, l, mid, sp, ep, v);
    if (ep > mid) update(rson, mid + 1, r, sp, ep, v);
    tr[rt].ans = tr[lson].ans + tr[rson].ans;
}

ll query(int rt, int l, int r, int sp, int ep)
{
    if (sp <= l && r <= ep) return tr[rt].ans;
    push_down(rt);
    ll res = 0;
    if (sp <= mid) res = query(lson, l, mid, sp, ep);
    if (ep > mid) res += query(rson, mid + 1, r, sp, ep);
    return res;
}

void addpath(int u) { while (u) update(1, 1, n, dfn[top[u]], dfn[u], 1), u = f[top[u]]; }

ll qrypath(int u) { ll res = 0; while (u) res += query(1, 1, n, dfn[top[u]], dfn[u]), u = f[top[u]]; return res; }

int main()
{
    cin.tie(0)->ios::sync_with_stdio(false);
    int w; cin >> t >> w;
    q[0] = { 1, ++n };
    a[n] = w;
    for (int i = 1; i <= t; i++) {
        int opt; cin >> opt;
        if (opt == 1) {
            int fa, w; cin >> w >> fa;
            q[i] = { opt, ++n };
            a[n] = w;
            e[fa].push_back(n), e[n].push_back(fa);
        } else q[i] = { opt, 0 };
    }
    dfs(1, 0), dfs1(1, 1);
    build(1, 1, n);
    ll sum = 0;
    for (int i = 0; i <= t; i++) {
        if (q[i].opt == 1) addpath(q[i].u), sum += qrypath(q[i].u);
        else cout << sum << "\n";
    }
    return 0;
}

T3

https://www.mxoj.net/problem/P60406

题意

你有 \(n\) 个点,每个点有颜色 \(a_i\)

一开始所有点都是 \(0\)

\(q\) 次操作,每次把所有颜色为 \(x\) 的点的状态反转,求有多少极长 \(1\) 的连续段。

\(n,q\leq 2\times 10^5\)

Solution

对于这种题,考虑根号分治。

如果你这个颜色有 \(>B\) 个,那么最多只会有 \(\frac{n}{B}\) 个颜色会是这种情况。

考虑答案如何计算:\(1\) 的个数 \(-\) 连续 \(11\) 的个数。

前面这个是好算的,考虑如何计算后面这个。

我们可以把相邻的两个点的颜色连边,边的边权就是两个颜色之间有多少点是相邻的,这个是好维护的。

然后如果你要修改的颜色的点数 \(\leq B\) 那么就可以通过这个暴力修改,因为至多才 \(2B\) 条边。

现在考虑如果修改的颜色的点数 \(> B\) 怎么办。

首先对于其他的和这个颜色相邻的大点,我们可以预处理他们的贡献。像暴力那样,因为只有 \(\frac{n}{B}\) 个大点,所以直接暴力就行。

然后考虑和这个颜色相邻的小点,我们可以在修改小点的时候把他们和大点的贡献记下来,挂到大点上去,这样修改大点的时候就可以直接处理掉这些小点的贡献。

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

Code

#include <bits/stdc++.h>

using namespace std;

const int N = 2e5 + 10, inf = 0x3f3f3f3f;

int n, q, k, a[N], sz[N], st[N], tag[N];
unordered_map<int, int> to[N];
vector<pair<int, int>> e[N], g[N];

int main()
{
    cin.tie(0)->ios::sync_with_stdio(false);
    cin >> n >> q >> k;
    const int B = sqrt(n);
    for (int i = 1; i <= n; i++) cin >> a[i], sz[a[i]]++;
    for (int i = 1; i < n; i++) {
        if (a[i] == a[i + 1]) sz[a[i]]--;
        else to[a[i]][a[i + 1]]++, to[a[i + 1]][a[i]]++;
    }
    for (int i = 1; i <= k; i++) for (auto j : to[i]) {
        int v, w; tie(v, w) = j;
        e[i].emplace_back(v, w);
    }
    for (int i = 1; i <= k; i++) if (e[i].size() > B) {
        for (auto j : e[i]) {
            int v, w; tie(v, w) = j;
            if (e[v].size() > B) g[i].emplace_back(v, w);
        }
    }
    for (int i = 1; i <= k; i++) st[i] = 1;
    int ans = 0;
    for (int i = 1; i <= q; i++) {
        int x; cin >> x;
        ans += st[x] * sz[x]; st[x] *= -1;
        if (e[x].size() <= B) {
            for (auto j : e[x]) {
                int v, w; tie(v, w) = j;
                if (st[v] == -1) ans -= st[x] * st[v] * w;
                tag[v] -= st[x] * w;
            }
        } else {
            ans += tag[x] * st[x];
            for (auto j : g[x]) {
                int v, w; tie(v, w) = j;
                if (st[v] == -1) ans -= st[x] * st[v] * w;
            }
        }
        cout << ans << "\n";
    }
    return 0;
}

T4

https://www.mxoj.net/problem/P14228?contestId=35

题意

给你一个 \(n\) 个点 \(m\) 条边的无向图。保证联通,没有重边自环。

你可以选择若干个联通子图,并留下这个子图的并(点的并,边的并)。

求最后最多留下多少连通块。

\(n \leq 10^5, m\leq 2\times 10^5\)

Solution

首先考虑一棵树,只能留下一个连通块。因为路径唯一。

考虑一个环,我们可以通过每次选择环上除了一条边的其他所有边和点,来把所有边的删掉。答案为环的大小。

考虑把图缩边双,那么每个边双都会贡献大小个连通块。考虑缩完边双后,整个图形成一颗树,每连接一条边就会使得答案 \(-1\)

那么设边双个数为 \(x\),则答案为 \(n - (x - 1)\)

Code

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10, inf = 0x3f3f3f3f;

int n, m, dfn[N], low[N], cnt;
bool f[N << 1], vis[N];
vector<pair<int, int>> e[N];

void dfs(int u, int fa)
{
	dfn[u] = low[u] = ++cnt;
	for (auto t : e[u]) {
		int v = t.first, id = t.second;
		if (!dfn[v]) {
			dfs(v, u);
			low[u] = min(low[u], low[v]);
			if (low[v] > dfn[u]) f[id] = 1;
		}
		else if (v != fa) low[u] = min(low[u], dfn[v]);
	}
}

int main()
{
    cin.tie(0)->ios::sync_with_stdio(false);
    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        int u, v; cin >> u >> v;
        e[u].emplace_back(v, i), e[v].emplace_back(u, i);
    }
    for (int i = 1; i <= n; i++) if (!dfn[i]) dfs(i, 0);
    auto dfs = [&](auto &&self, int u) -> void {
        vis[u] = 1;
        for (auto t : e[u]) {
            int v = t.first, id = t.second;
            if (vis[v] || f[id]) continue;
            self(self, v);
        }
    };
    int c = 0;
    for (int i = 1; i <= n; i++) if (!vis[i]) {
        c++;
        dfs(dfs, i);
    }
    cout << n - (c - 1);
    return 0;
}
posted @ 2025-08-06 22:50  Dtwww  阅读(61)  评论(0)    收藏  举报