Codeforces 2245 D2 / E / F 题解

D2. Construct an Array (Hard Version)

题目链接:Codeforces 2245D2

算法标签:图论2-SAT 思想贪心构造

思路

把每条限制看成一条带类型的无向边:类型 1 要求两端之和非负,类型 2 要求两端之和为负。对于当前还没有处理的点,如果它只连接一种类型的边,就可以直接确定它的正负号:只剩类型 1 边就赋一个较大的正数,只剩类型 2 边就赋一个绝对值较大的负数。

用队列不断删除这样的点,并令最先删除的点绝对值最大,依次取 n,n-1,...,1。处理点 u 时,它的绝对值大于所有尚未处理的相邻点,因此它可以主导两数之和的符号:正数一定满足剩余的类型 1 边,负数一定满足剩余的类型 2 边。删点后更新邻点两类边的数量,新出现的可删点继续入队。

如果最后还有点没有删除,那么剩余图中每个点都同时连接两种边。取绝对值最大的点:若它为正,与它相连的类型 2 边要求出现绝对值更大的负数;若它为负,其类型 1 邻点必须是绝对值不小于它的正数,再沿这个正数的一条类型 2 边同样会要求更大的负数,均矛盾。因此此时无解。

时间复杂度为 O(n+m),空间复杂度为 O(n+m)

代码

cpp 复制代码
#include <bits/stdc++.h>

using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t;
    cin >> t;
    while (t--) {
        int n, m;
        cin >> n >> m;
        vector<vector<int>> edge(n + 1);
        vector<array<int, 2>> d(n + 1);
        while (m--) {
            int o, u, v;
            cin >> o >> u >> v; o--;
            edge[u].push_back(o ? -v : v);
            edge[v].push_back(o ? -u : u);
            d[u][o]++;
            d[v][o]++;
        }
        vector<int> ans(n + 1);
        vector<bool> vis(n + 1);
        queue<int> q;
        for (int u = 1; u <= n; u++) {
            if (!d[u][0] || !d[u][1]) {
                q.push(u);
                vis[u] = true;
            }
        }
        int cur = n;
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            ans[u] = (d[u][0] ? 1 : -1) * cur--;
            for (int v : edge[u]) {
                int o = v < 0;
                v = abs(v);
                d[v][o]--;
                if (!vis[v] && (!d[v][0] || !d[v][1])) {
                    q.push(v);
                    vis[v] = true;
                }
            }
        }
        if (cur) cout << "NO\n";
        else {
            cout << "YES\n";
            for (int i = 1; i <= n; i++) cout << ans[i] << " \n"[i == n];
        }
    }
}

E. Tom and Jerry

题目链接:Codeforces 2245E

算法标签:博弈论树形 DPDFS

思路

这道博弈的关键结论是:第一步选择的路径为必胜路径,当且仅当路径的两个端点度数为奇数,所有内部点的度数为偶数。这个结论可以对后续游戏状态归纳得到;直观上,路径端点已经使用 1 条边、内部点已经使用 2 条边,所以恰好要求路径上每个点剩余的出边数都是偶数。

接下来只需统计这样的路径。将树以 1 为根,dp[u] 表示 u 的子树中,有多少个奇度点可以沿着一条内部全为偶度点的链连接到 u

  • u 的度数为奇数,u 只能作为路径端点,不能作为内部点。每个儿子的 dp[v] 都能和 u 组成一条合法路径,所以加入答案,并令 dp[u]=1
  • u 的度数为偶数,路径可以穿过 u。从两个不同儿子子树中各取一个奇度端点即可组成合法路径,因此累加所有 dp[v] 的两两乘积;同时令 dp[u] 为所有儿子 dp[v] 之和,继续向父亲传递。

时间复杂度为 O(n),空间复杂度为 O(n)

代码

cpp 复制代码
#include <bits/stdc++.h>

using namespace std;

vector<vector<int>> edge;
vector<long long> dp;
long long ans;

void dfs(int u, int fa) {
    long long sum = 0;
    bool odd = edge[u].size() % 2;
    for (int v : edge[u]) {
        if (v == fa) {
            continue;
        }
        dfs(v, u);
        if (odd) {
            ans += dp[v];
        } else {
            ans += dp[v] * sum;
            sum += dp[v];
        }
    }
    dp[u] = odd ? 1 : sum;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        edge.assign(n + 1, {});
        dp.assign(n + 1, 0);
        for (int i = 1; i < n; i++) {
            int u, v;
            cin >> u >> v;
            edge[u].push_back(v);
            edge[v].push_back(u);
        }
        ans = 0;
        dfs(1, 0);
        cout << ans << '\n';
    }
}

F. Familiar?

题目链接:Codeforces 2245F

算法标签:区间 DP笛卡尔树思想组合数学单调栈计数

思路

处理一个区间 [l,r],设区间最小值出现在位置 k。执行到 k 时,它会把左半段最后留在单调栈中的元素全部弹出,所以 b[k] 就等于左区间处理结束后的栈大小。最小值入栈后不会再被右半段弹出,因此整个区间最终的栈大小等于右区间最终栈大小加一。

定义 dp[l][r][c] 表示区间 [l,r] 满足已知限制,并且最终栈大小为 c 的排列数;g[l][r] 表示不限制最终栈大小的排列数。枚举最小值位置 k:左边需要满足 a[k] 指定的栈大小,右边决定最终栈大小。除最小值外,还要从其余 r-l 个数中选择 k-l 个放到左边,因此乘上组合数 C(r-l,k-l)

a[k]=-1 时,左边可以取任意栈大小,使用 g[l][k-1];否则使用 dp[l][k-1][a[k]]。计算 g 时右边使用 g[k+1][r],计算最终栈大小为 cdp 时右边使用 dp[k+1][r][c-1]。代码令 a[n+1]=-1,所以最后答案就是 g[1][n]

一次完整过程最多弹出 n-1 个元素,因此已知弹出次数之和若不小于 n 可以直接输出 0。这个判断也保证了代码实际开出的 dp 状态总数为 O(n^2)。总时间复杂度为 O(n^3),空间复杂度为 O(n^2)

代码

cpp 复制代码
#include <bits/stdc++.h>

using namespace std;

const int mod = 998244353;
int C[501][501];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    C[0][0] = 1;
    for (int i = 1; i <= 500; i++) {
        C[i][0] = C[i][i] = 1;
        for (int j = 1; j < i; j++) {
            C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod;
        }
    }

    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        vector<int> a(n + 2);
        int sum = 0;
        for (int i = 1; i <= n; i++) {
            cin >> a[i];
            sum += max(a[i], 0);
        }
        if (sum >= n) {
            cout << 0 << '\n';
            continue;
        }

        a[n + 1] = -1;
        vector<vector<vector<int>>> dp(n + 2, vector<vector<int>>(n + 2));
        vector<vector<int>> g(n + 2, vector<int>(n + 2));

        for (int r = 1; r < n; r++) {
            if (a[r + 1] != -1) {
                for (int l = 1; l <= r; l++) {
                    dp[l][r].resize(a[r + 1] + 1);
                }
            }
        }
        for (int i = 1; i <= n; i++) {
            if (a[i] <= 0) {
                if (a[i + 1] == -1) {
                    g[i][i] = 1;
                } else if (a[i + 1] > 0) {
                    dp[i][i][1] = 1;
                }
            }
        }

        for (int r = 2; r <= n; r++) {
            for (int l = r - 1; l >= 1; l--) {
                if (a[r + 1] == -1) {
                    // g[l][r] = sum(g[k+1][r] * C[r-l][k-1])
                    long long cur = 0;
                    for (int k = l; k <= r; k++) {
                        int left, right;
                        if (k != l && a[k] == -1) left = g[l][k - 1];
                        else if (k != l && a[k] != -1) left = dp[l][k - 1][a[k]];
                        else if (k == l) left = a[k] <= 0;

                        if (k != r) right = g[k + 1][r];
                        else if (k == r) right = 1;
                        cur += 1LL * C[r - l][k - l] * left % mod * right % mod;
                    }
                    g[l][r] = cur % mod;
                } else {
                    // have a[r + 1]
                    // dp[l][r][c] = sum(dp[l][k-1][a[k]] * dp[k+1][r][c-1] * C[r-l][k-l])
                    for (int c = 1; c <= a[r + 1]; c++) {
                        long long cur = 0;
                        for (int k = l; k <= r; k++) {
                            int left, right;
                            if (k != l && a[k] == -1) left = g[l][k - 1];
                            else if (k != l && a[k] != -1) left = dp[l][k - 1][a[k]];
                            else if (k == l) left = a[k] <= 0;

                            if (k != r) right = dp[k + 1][r][c - 1];
                            else if (k == r) right = (c == 1);
                            cur += 1LL * C[r - l][k - l] * left % mod * right % mod;
                        }
                        dp[l][r][c] = cur % mod;
                    }
                }
            }
        }
        cout << g[1][n] << '\n';
    }
}
相关推荐
薛定e的猫咪5 小时前
在 vibe coding开发项目过程中使用过的命令和概念整理
人工智能·学习·算法·开源
Tisfy5 小时前
LeetCode 1979.找出数组的最大公约数:模拟(附手动gcd)
java·数学·算法·leetcode·题解·最大公约数
圣光SG5 小时前
Excel 函数大全
算法·excel
To_OC6 小时前
LC 79 单词搜索:都说这是回溯入门题,我却连错三回
javascript·算法·leetcode
问商十三载7 小时前
2026大模型GEO内链优化:3个传导逻辑提权重,零成本提29%收录优先级附布局表
人工智能·算法
Risk Actuary8 小时前
手动示例解释机器学习中 GBDT 算法原理
人工智能·算法·机器学习
Lucis__9 小时前
基于Cache替换算法的LRU缓存实现
数据结构·c++·算法·缓存·lru
血色橄榄枝14 小时前
基于用户注册信息的关键词检测挑战赛「Datawhale AI 夏令营」
人工智能·算法·机器学习
c2385615 小时前
第二篇:《测试指挥官:可视化单题自测框架(含 assert 实操)》
java·数据库·c++·算法·安全性测试