AtCoder Beginner Contest 396(d和e)

题目链接d

题目分析

本题要求在一个简单连通无向图中,找出从顶点 1 到顶点 N 的所有简单路径(即不重复经过同一顶点的路径)中,路径上所有边的标签的异或值的最小值。

输入信息

  • 第一行包含两个整数 NM,分别表示图的顶点数和边数,其中 2 ≤ N ≤ 10N - 1 ≤ M ≤ N * (N - 1) / 2
  • 接下来的 M 行,每行包含三个整数 u_iv_iw_i,表示顶点 u_iv_i 之间有一条标签为 w_i 的边,其中 1 ≤ u_i < v_i ≤ N0 ≤ w_i < 2^60

输出信息

输出从顶点 1 到顶点 N 的所有简单路径中,边标签异或值的最小值。

解题思路

由于顶点数 N 最大为 10,所以可以使用深度优先搜索(DFS)来遍历所有从顶点 1 到顶点 N 的简单路径,并计算每条路径上所有边的标签的异或值,最后取最小值。

具体步骤如下:

  1. 构建图的邻接表:使用邻接表来存储图的结构,其中每个顶点的邻接表存储了与该顶点相邻的顶点以及对应的边的标签。
  2. 初始化变量:初始化一个布尔数组 visited 来标记每个顶点是否被访问过,以及一个变量 min_xor 来记录最小的异或值,初始值设为 LLONG_MAX
  3. 深度优先搜索:从顶点 1 开始进行深度优先搜索,对于每个未访问过的相邻顶点,递归调用 DFS 函数,并更新当前的异或值。当到达顶点 N 时,更新最小异或值。
  4. 回溯:在递归返回时,将当前顶点标记为未访问,以便尝试其他路径。
  5. 输出结果:最后输出最小异或值。

代码实现

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

// 定义邻接表,存储图的结构
vector<pair<int, ll>> adj[10];
// 标记每个顶点是否被访问过
bool visited[10];
// 记录最小的异或值
ll min_xor = LLONG_MAX;

// 深度优先搜索函数
void dfs(int u, int N, ll current_xor) {
    // 如果到达顶点 N,更新最小异或值
    if (u == N - 1) {
        min_xor = min(min_xor, current_xor);
        return;
    }
    // 标记当前顶点为已访问
    visited[u] = true;
    // 遍历当前顶点的所有相邻顶点
    for (auto &[v, w] : adj[u]) {
        // 如果相邻顶点未被访问过
        if (!visited[v]) {
            // 递归调用 DFS 函数,并更新当前的异或值
            dfs(v, N, current_xor ^ w);
        }
    }
    // 回溯,将当前顶点标记为未访问
    visited[u] = false;
}

int main() {
    int N, M;
    cin >> N >> M;
    // 读取每条边的信息,并构建图的邻接表
    for (int i = 0; i < M; i++) {
        int u, v;
        ll w;
        cin >> u >> v >> w;
        // 顶点编号从 0 开始,所以减 1
        u--; v--;
        adj[u].push_back({v, w});
        adj[v].push_back({u, w});
    }
    // 初始化 visited 数组为 false
    memset(visited, false, sizeof(visited));
    // 从顶点 0 开始进行深度优先搜索,初始异或值为 0
    dfs(0, N, 0);
    // 输出最小异或值
    cout << min_xor << endl;
    return 0;
}

注意事项

  • 由于边的标签 w_i 可能非常大,需要使用 long long 类型来存储。

题目链接e

题目概述

问题描述
给定以下输入:
(N):序列 (A) 的长度
(M):约束条件的数量
三个长度为 (M) 的序列:(X)、(Y) 和 (Z),对于每个 (i)(1≤i≤M):
AXi⊕AYi=Zi(其中 ⊕表示异或运算)
任务:
判断是否存在一个非负整数序列 A=(A1,A2,…,AN),使得所有约束条件都得到满足
如果存在,找到一个使 数组A和最小的序列。
如果不存在,输出 −1
解题思路

  1. 理解异或约束
    异或运算的性质是:若 A⊕B=C,则 A=B⊕C 或 B=A⊕C
    。这意味着给定一个约束 AXi⊕AYi=Zi,如果我们确定了 AXi,就可以计算 AYi,反之亦然。问题的核心在于:
  • 确保所有 (M) 个约束条件之间的一致性。

  • 在满足约束时,最小化序列的总和。

  1. 建模为图
    我们可以将问题抽象为一个无向图:
  • 节点:序列中的位置 1,2,…,N,对应 A1,A2,…,AN
  • 边:每个约束 AXi⊕AYi=Zi表示一条连接 Xi和 Yi的边,边的“权重”为 Zi
    ,表示异或关系。
    图可能包含多个连通分量,每个连通分量内的值必须满足所有相关约束。
  1. 检查一致性
    要确保序列 (A) 存在,必须验证图中每个连通分量的赋值一致性。如果一个节点可以通过多条路径到达,则通过不同路径计算出的值必须相等。我们使用DFS来检查这一点。
  2. 最小化总和
    由于 Ai是非负整数,最小值为 0。我们的目标是使每个 Ai尽可能小:
    为每个连通分量分配初始值(例如从 0 开始)
    根据约束传播值。
    通过调整整个连通分量的值(异或一个常数),使总和最小化

解决方案步骤
步骤 1:构建图
对于每个约束 (i),在节点 Xi和 Yi之间添加一条边,权重为 Zi
使用邻接表存储图。
步骤 2:DFS 遍历与一致性检查
对于每个未访问的节点,运行 DFS:

  • 从当前节点(设为根 (u))开始,假设 Au=0
  • 对于每个邻居 (v)(边权重为 (w)),计算 Av=Au⊕w
  • 如果 (v) 未访问,继续递归。
  • 如果 (v) 已访问,检查 Av是否等于 Au⊕w若不相等,则约束不一致,返回 −1

步骤 3:最小化总和

  • DFS 确定每个连通分量的值后,可以通过异或一个掩码((msk))调整整个连通分量的值。
  • 对于每个位 (k)(0 到 29,因 Zi≤109<230):
    • 统计连通分量中第 (k) 位为 1 的节点数。
    • 如果超过一半的节点该位为 1,则翻转该位(异或 1<<k),使更多值为 0


这样保证总和最小。
步骤 4:输出结果
如果所有连通分量一致,输出最终序列 (A)
否则,输出 −1-1-1

示例说明
输入:

5 8
4 2 4
2 3 11
3 4 15
4 5 6
3 2 11
3 3 0
3 1 9
3 4 15

分析:
DFS:
A3=0
A1=0⊕9=9
A2=0⊕11=11
A4=0⊕15=15
A5=15⊕6=9
验证其他约束,一致。

最小化:初始 A=(9,11,0,15,9),和为 44,
根据异或性质设置掩码

对掩码异或调整后得 A=(0,2,9,6,0)和为17。
完整代码

#include<iostream>
#include<vector>
#include<cassert>
#include<cstdlib>
using namespace std;
int N, M;
vector<pair<int,int>> G[2<<17];// 邻接表 
bool vis[2<<17];
vector<int> V;// 存储当前连通分量中的所有节点
int W[2<<17];// 存储序列 A 中各元素的值

// u 表示当前正在访问的节点,w 表示当前节点 u 的值
void dfs(int u, int w) {
    vis[u] = true;
    V.push_back(u);
    W[u] = w;
    for (auto [v, e] : G[u]) {
        if (!vis[v]) dfs(v, w ^ e);
        // 如果相邻节点 v 已经被访问过
        else if (W[v] != (w ^ e)) {
            // 说明根据当前路径计算出的相邻节点 v 的值与之前记录的值不一致,约束条件冲突
            cout << -1 << endl;
            // 退出整个程序,因为一旦发现不一致就无需继续处理,return不行
            exit(0);
        }
    }
}

int main() {
    ios::sync_with_stdio(0),cin.tie(0);
    cin >> N >> M;
    for (int i = 0; i < M; i++) {
        int u, v, w;
        // 读取每个约束条件中的两个节点编号 u 和 v 以及它们异或的结果 w
        cin >> u >> v >> w;
        u--, v--;
        // 在节点 u 的邻接表中添加相邻节点 v 及边的权重 w,无向图
        G[u].push_back({v, w});
        G[v].push_back({u, w});
    }
    for (int i = 0; i < N; i++) {
        if (!vis[i]) {
            V.clear();
            dfs(i, 0);
            // 用于统计当前连通分量中每个二进制位为 1 的节点数量
            int cnt[30] = {};
            for (int u : V) {
                // 遍历每个二进制位(从第 0 位到第 29 位)
                for (int k = 0; k < 30; k++) {
                    // 如果节点 u 的第 k 位为 1
                    if (W[u] >> k & 1) cnt[k]++;
                }
            }
            // 用于存储需要异或的掩码,用于调整连通分量中所有节点的值以最小化总和
            int msk = 0;
            for (int k = 0; k < 30; k++) {
                // 如果当前连通分量中第 k 位为 1 的节点数量超过总数的一半
                if (cnt[k] > V.size() - cnt[k]) {
                    // 将第 k 位置为 1 到掩码 msk 中
                    msk |= 1 << k;
                }
            }
            for (int u : V) {
                // 将每个节点的值异或掩码 msk,以最小化连通分量中所有节点值的总和
                W[u] ^= msk;
            }
        }
    }
    for (int i = 0; i < N; i++) cout << W[i]<< " ";
    return 0;
}
posted @ 2025-03-09 16:34  fufuaifufu  阅读(79)  评论(0)    收藏  举报