「解题报告」P9170 [省选联考 2023] 填数游戏

考场上读错题了,而且也没想到树的情况怎么做。考场上的思路也很乱,感觉根本无从下手。

感觉心态还是决定很多东西的。今天写这个题一步一步写出来,自我感觉还满清楚的。

这种题从特殊性质入手。

首先特殊性质 A 让我们判定 Bob 是否有解。

由于集合大小为 \(2\)\(1\) 可以复制一遍变为 \(2\)),我们可以将它建出图来,每个集合看作一条边。那么 Bob 选择互不相同的限制其实就是在图上,每个边选择两个端点之一,且不能选择重复的端点。这个是很容易的,因为对于一个连通块来说,要不然有 \(n - 1\) 条边,要不然有 \(n\) 条边,如果边再多显然不合法,因为点的个数不够选的。而容易发现,树与基环树的情况一定存在合法方案。那么我们只需要拿并查集维护一下,看看最后得到的图中是否都是树或基环树即可。

再考虑特殊性质 B,发现是一个环。这个环对应着基环树的情况。容易发现,在这个环上,Bob 只有两种选择方案,要不然都选左边的点,要不然都选右边的点。那么对于每一种选择方案,我们可以计算出 Alice 选哪些点能够使得答案加 1。可以发现,分为了三种情况,一种是 Alice 可以在两个端点中任选一个,这时候可以对两种方案之一造成贡献,第二种情况是 Alice 只能选两个端点中的一个,只能对一种方案造成贡献,第三种情况就是都选不了,都造成不了贡献。那么假如设 \(a, b, c\) 分别表示只能对第一种选择方案造成的贡献,只能对第二种方案造成的贡献与两者均可造成的贡献,那么我们就是要最大化 \(\min_{0 \le i \le c}(a + i, b + c - i)\),容易分情况讨论得到答案为 \(\begin{cases}\lfloor\frac{a + b + c}{2}\rfloor&|a-b|\le c\\\min(a, b)+c&|a - b|>c\end{cases}\)。注意特判一下环是一个自环的情况。

对于基环树来说,发现环上的选择方案都是确定了的,那么向外连的树的方案其实是唯一固定的,这时候 Alice 容易得出贡献最大的选择方案。

下面考虑树的情况。首先发现,Bob 有 \(n\) 种选择方案,即选择一个点不选,其他的都往外选。但是 Alice 有 \(2^n\) 种选择方案,还是很难考虑。考虑对于 Bob 的每一种选择方案都维护出一个答案,那么对于 Alice 来说,如果 \(u-v\) 的一条边中选择 \(u\),那么就会对 \(v\) 子树中的所有点造成 \(1\) 的贡献。

我们随便取一个点为根,那么每一个点的贡献相当于是给子树内或子树外的所有点进行贡献。我们可以通过分析一些情况来减少 Alice 的选择方案:

  • 假如存在两个点 \(u, v\) 不存在祖先关系,且满足 \(u\) 向子树内贡献,\(v\) 也向子树内贡献,那么显然不如 \(u\) 向子树外贡献,\(v\) 向子树外贡献更优;
  • 假如 \(u\)\(v\) 的祖先,且满足 \(u\) 向子树外贡献,\(v\) 向子树内贡献,那么显然不如 \(u\) 向子树内贡献,\(v\) 向子树外贡献。

综合上述两种情况,我们可以得出结论:Alice 只会将根到某个点的所有边选择向子树内贡献,其它的点都会选择向子树外贡献(这等价于存在一个点 \(u\),使得所有的边都会向 \(u\) 方向贡献)。这意味着 Alice 只有 \(n\) 种方案。

我们只需要对这 \(n\) 种选择方案快速维护出 Bob 的答案即可,可以 DFS 一遍,然后每次对子树内或子树外加减 1 即可维护出答案。我直接拿线段树维护的。

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1000005;
int T;
int n, m, su[MAXN], sv[MAXN], tu[MAXN], tv[MAXN];
int fa[MAXN], ce[MAXN];
bool cyl[MAXN];
vector<pair<int, int>> e[MAXN];
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
bool merge(int u, int v, int i) {
    int ru = find(u), rv = find(v);
    if (ru == rv) {
        if (ce[ru]) return false;
        ce[ru] = i;
    } else {
        if (ce[ru] && ce[rv]) return false;
        fa[ru] = rv, ce[rv] |= ce[ru];
        e[u].push_back({ v, i }), e[v].push_back({ u, i });
    }
    return true;
}
int f[MAXN], dep[MAXN], dfn[MAXN], ed[MAXN], dcnt;
int fe[MAXN];
void dfs(int u, int pre) {
    f[u] = pre, dep[u] = dep[pre] + 1;
    dfn[u] = ++dcnt;
    for (auto [v, i] : e[u]) if (v != pre) {
        dfs(v, u);
        fe[v] = i;
    }
    ed[u] = dcnt;
}
pair<vector<int>, vector<int>> findCycle(int u, int v) {
    vector<int> a, b;
    while (dep[u] > dep[v]) cyl[u] = 1, a.push_back(u), u = f[u];
    while (dep[u] < dep[v]) cyl[v] = 1, b.push_back(v), v = f[v];
    while (u != v) cyl[u] = 1, a.push_back(u), u = f[u], cyl[v] = 1, b.push_back(v), v = f[v];
    reverse(b.begin(), b.end());
    vector<int> ed;
    for (int i : a) ed.push_back(fe[i]);
    for (int i : b) ed.push_back(fe[i]);
    cyl[u] = 1, a.push_back(u);
    for (int i : b) a.push_back(i);
    return { a, ed };
}
int solveCycleTree(int u, int pre, int i = 0) {
    int ans = 0;
    if (pre) {
        if (su[i] == u || sv[i] == u) ans++;
    }
    for (auto [v, i] : e[u]) if (v != pre && !cyl[v]) {
        ans += solveCycleTree(v, u, i);
    }
    return ans;
}
int cid[MAXN], cu[MAXN], cv[MAXN];
struct SegmentTree {
    struct Node {
        int mn, tag;
    } t[MAXN << 2];
#define lc (i << 1)
#define rc (i << 1 | 1)
    void build(int i = 1, int l = 1, int r = dcnt) {
        t[i].mn = t[i].tag = 0;
        if (l == r) return;
        int mid = (l + r) >> 1;
        build(lc, l, mid), build(rc, mid + 1, r);
    }
    inline void tag(int i, int v) { t[i].mn += v, t[i].tag += v; }
    inline void pushDown(int i) { if (t[i].tag) tag(lc, t[i].tag), tag(rc, t[i].tag), t[i].tag = 0; }
    void add(int a, int b, int v, int i = 1, int l = 1, int r = dcnt) {
        if (a <= l && r <= b) return tag(i, v);
        int mid = (l + r) >> 1;
        pushDown(i);
        if (a <= mid) add(a, b, v, lc, l, mid);
        if (b > mid) add(a, b, v, rc, mid + 1, r);
        t[i].mn = min(t[lc].mn, t[rc].mn);
    }
} st;
void precompute(int u, int pre, int i = 0) {
    if (pre) {
        if ((u == su[i] && pre == sv[i]) || (u == sv[i] && pre == su[i])) {
            st.add(1, dcnt, 1), st.add(dfn[u], ed[u], -1);
        } else if (u == su[i] || u == sv[i]) {
            st.add(1, dcnt, 1), st.add(dfn[u], ed[u], -1);
        } else if (pre == su[i] || pre == sv[i]) {
            st.add(dfn[u], ed[u], 1);
        }
    }
    for (auto [v, i] : e[u]) if (v != pre) {
        precompute(v, u, i);
    }
}
int fans;
void solveTree(int u, int pre, int i = 0) {
    if (pre) {
        if ((u == su[i] && pre == sv[i]) || (u == sv[i] && pre == su[i])) {
            st.add(1, dcnt, -1), st.add(dfn[u], ed[u], 2);
        }
    }
    fans = max(fans, st.t[1].mn);
    for (auto [v, i] : e[u]) if (v != pre) {
        solveTree(v, u, i);
    }
    if (pre) {
        if ((u == su[i] && pre == sv[i]) || (u == sv[i] && pre == su[i])) {
            st.add(1, dcnt, 1), st.add(dfn[u], ed[u], -2);
        }
    }
}
int main() {
    // freopen("game9.in", "r", stdin);
    // freopen("game9.out", "w", stdout);
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; i++) {
            int c; scanf("%d%d", &c, &su[i]);
            if (c == 1) sv[i] = su[i];
            else scanf("%d", &sv[i]);
        }
        for (int i = 1; i <= n; i++) {
            int c; scanf("%d%d", &c, &tu[i]);
            if (c == 1) tv[i] = tu[i];
            else scanf("%d", &tv[i]);
        }
        // MULTITASK CLEARING START
        for (int i = 1; i <= m; i++)
            fa[i] = i, ce[i] = cyl[i] = 0, e[i].clear();
        // MULTITASK CLEARING END
        bool flag = true;
        for (int i = 1; i <= n; i++) {
            if (!merge(tu[i], tv[i], i)) {
                printf("-1\n");
                flag = false;
                break;
            }
        }
        // continue;
        if (!flag) continue;
        int ans = 0;
        for (int i = 1; i <= m; i++) if (fa[i] == i) {
            if (ce[i]) {
                // find the cycle
                dfs(i, 0);
                auto [p, q] = findCycle(tu[ce[i]], tv[ce[i]]);
                // calc tree part
                for (int i : p) {
                    ans += solveCycleTree(i, 0);
                }
                // calc cycle part
                int k = p.size();
                if (k == 1) {
                    if (p[0] == su[ce[i]] || p[0] == sv[ce[i]]) {
                        ans++;
                    }
                } else {
                    for (int j = 1; j < k; j++) {
                        cid[j] = q[j - 1], cu[j] = p[j - 1], cv[j] = p[j];
                    }
                    cid[k] = ce[i], cu[k] = tv[ce[i]], cv[k] = tu[ce[i]];
                    int a = 0, b = 0, c = 0;
                    for (int j = 1; j <= k; j++) {
                        if ((cu[j] == su[cid[j]] && cv[j] == sv[cid[j]]) || 
                            (cv[j] == su[cid[j]] && cu[j] == sv[cid[j]])) {
                            c++;
                        } else if (cu[j] == su[cid[j]] || cu[j] == sv[cid[j]]) {
                            a++;
                        } else if (cv[j] == su[cid[j]] || cv[j] == sv[cid[j]]) {
                            b++;
                        }
                    }
                    if (a > b) swap(a, b);
                    if (a + c <= b) ans += a + c;
                    else ans += (a + b + c) / 2;
                }
            } else {
                // precompute dfn
                dcnt = 0;
                dfs(i, 0);
                st.build();
                // fixing & precomputing selection
                precompute(i, 0);
                // calc ans
                fans = 0;
                solveTree(i, 0);
                ans += fans;
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}
posted @ 2023-04-10 18:25  APJifengc  阅读(92)  评论(4编辑  收藏  举报