【算法导论】MT 0823笔试题题解

整齐摆放

n个长方形,第i个长方形的两条边长len1[i]、len2[i]已知。另外我们有一个只包含第一象限的平面直角坐标系。

现在我们将这n个长方形依次摆放在坐标系的x轴上,不允许重叠,且每个长方形放置后在y轴上的高度不超过m。

题目保证max(min(len1, len2))<=m

求摆放完成所有长方形,至少需要占用x轴的多少长度?

C++ 复制代码
#include <bits/stdc++.h>
using namespace std;

int main() {
    int n;  // n 个长方形
    int64_t m;  // 每个长方形放置后的高度不超过m
    std::cin >> n >> m;

    // 读取每个长方形的边长
    std::vector<int64_t> len1(n);
    std::vector<int64_t> len2(n);
    for (int i = 0; i < n; ++i) {
        std::cin >> len1[i] >> len2[i];
    }

    // 将n个长方形放置到x轴上
    // 最少需要占用的x轴的长度
    int64_t ans = 0;
    for (int i = 0; i < n; ++i) {
        // 先看max_len是否超出m,如果不超出用min_len
        // 如果max_len超出m,则只能用max_len
        int64_t max_len = std::max(len1[i], len2[i]);
        int64_t min_len = std::min(len1[i], len2[i]);
        if (max_len <= m) {
            ans += min_len;
        } else {
            ans += max_len;
        }
    }

    std::cout << ans;
}

注意题目中的数据范围已经达到2^9这个数量级,因此需要使用int64_t。

美丽数组

我们有一个感兴趣的数x,已知0<=x<2^20。

现在我们寻找一个美丽数组。如果一个数组a满足以下条件,则我们称它为美丽数组:

  1. 数组a中的每个元素a[i],都有a[i]<2^20
  2. 数组a中不包含重复的元素
  3. 额外地,如果数组a的长度大于1,我们还要求:对于数组a中任意两个元素a[i]和a[j](a[i]≠a[j]),都有a[i]&a[j]==x(其中&指的是按位与运算)。

现在给定一个已知的x,请你求出满足上述条件的一个最长的美丽数组,输出它的长度和各个数组元素。

如果存在多个最长的美丽数组,输出任意一个即可

测试用例

输入

复制代码
2
1048575
1048573

输出

复制代码
1
1048575
2
1048573 1048575

题解

首先通过理解题意,明确这是一道构造题。

既然题目要求对美丽数组中的任意元素a[i]和a[j],都有a[i]&a[j]==x,那么一定有以下推论:

  1. x上的某一位为1,则a[i]和a[j]中该位一定为均为1
  2. x上的某一位为0,则a[i]和a[j]至少有一者,该位为0

我们先来看推论1。它的一个更弱的表述是,对于美丽数组中的任意一个元素a[k],如果x上的某一位为1,则a[k]中该位一定为均为1。即a[k]中至少包含了x中值为1的二进制位。

至于推论2,又该如何保证呢?

首先,对于构造出来数组元素a[k],我们当然可以直接躺平,不在x的基础上设置任何额外的值为1的二进制位。但这样美丽数组中就始终只有x一个元素了,不符合题目中最长的要求。

因此我们可以再往前走一步,对于每个x上为0的二进制位,我们只让美丽数组中一个元素在该位上为1,而其他元素中该位均为0。这样既可以保证我们构造出来的美丽数组符合推论2,又可以让它的长度达到最长。

C++ 复制代码
#include <iostream>
#include <vector>

int main() {
	int t;
	std::cin >> t;
	while (t-- > 0) {
		int x;
		std::cin >> x;
		std::vector<int> ans;
		ans.push_back(x);

		int peek = 1;
		for (int i = 0; i < 20; ++i) {
			if ((peek & x) == 0) {
				ans.push_back(x + peek);
			}
			peek <<= 1;
		}

		std::cout << ans.size() << std::endl;
		for (int i = 0; i < ans.size(); ++i) {
			std::cout << ans[i];
			if (i != ans.size() - 1) std::cout << " ";
		}
		std::cout << std::endl;
	}
}

带权距离

给定一棵以节点1为根的树,树上共有n个节点,其中某些节点被标记为"红色"。树上的每条无向边具有一个正整数权重w[u][v]。

现在在这棵树上执行q次操作<t, v>。每次操作有两种类型:

  1. 若t=1,表示刷新节点v的颜色状态。如原先v的颜色为非红,则变为红色;反之亦然。该操作不会打印任何内容。
  2. 若t-2,表示查询并打印节点v到所有红色节点的带权距离之和。

带权距离指的是路径上所有边的权重的总和

输入描述

第一行输入n、q。其中1<=n, q<=2*10^5。

第二行输入n个整数c[1]、c[2]、...、c[n]。其中c[i]∈{0, 1}。当c[i]=1,表示节点i为红色,否则则为非红。

接下来n-1行,每行输入三个整数u、v、w,表示树上的一条带权重无向边。

最后q行,每行输入一个t和v(t∈{1, 2},1<=v<=n),表示一次操作。

暴力解法

以下解法会导致空间超出限制:

C++ 复制代码
#include <bits/stdc++.h>
using namespace std;

// state[i] 节点i是不是红色的
std::vector<bool> state;

std::vector<std::vector<int>> graph;

std::unordered_set<int> vis;
int ComputePathSum(int n, const int curr, int path) {
    int sum = 0;
    for (int next = 0; next < n; ++next) {
        if (graph[curr][next] && vis.count(next) == 0) {
            if (state[next]) {
                sum += path + graph[curr][next];
            }
            vis.insert(next);
            sum += ComputePathSum(n, next, path + graph[curr][next]);
        }
    }
    return sum;
}

int main() {
    int n;  // 节点数
    int q;  // 操作数
    std::cin >> n >> q;

    state.assign(n, false);
    for (int i = 0; i < n; ++i) {
        int t;
        std::cin >> t;
        state[i] = t;
    }

    graph.assign(n, std::vector<int>(n, 0));
    for (int i = 0; i < n - 1; ++i) {
        int u, v, w;
        std::cin >> u >> v >> w;
        graph[u - 1][v - 1] = w;
        graph[v - 1][u - 1] = w;
    }

    for (int  i = 0; i < q; ++i) {
        int t, v;
        std::cin >> t >> v;
        if (t == 1) {
            state[v - 1] = !state[v - 1];
        } else if (t == 2) {
            // 查询节点v到所有红色节点的带权距离和
            vis.clear();
            vis.insert(v - 1);
            std::cout << ComputePathSum(n, v - 1, 0) << std::endl;
        }
    }
}

以下解法会导致时间超出限制:

C++ 复制代码
#include <bits/stdc++.h>
using namespace std;

// state[i] 节点i是不是红色的
std::vector<bool> state;

std::vector<std::vector<std::pair<int, int>>> graph;

std::unordered_set<int> vis;
int ComputePathSum(int n, const int curr, int path) {
    int sum = 0;

    for (auto [next, w] : graph[curr]) {
        if (vis.count(next) == 0) {
            if (state[next]) {
                sum += path + w;
            }
            vis.insert(next);
            sum += ComputePathSum(n, next, path + w);
        }
    }

    return sum;
}

int main() {
    int n;  // 节点数
    int q;  // 操作数
    std::cin >> n >> q;

    state.assign(n, false);
    for (int i = 0; i < n; ++i) {
        int t;
        std::cin >> t;
        state[i] = t;
    }

    graph.resize(n);
    for (int i = 0; i < n - 1; ++i) {
        int u, v, w;
        std::cin >> u >> v >> w;
        graph[u - 1].push_back({v - 1, w});
        graph[v - 1].push_back({u - 1, w});
    }

    for (int  i = 0; i < q; ++i) {
        int t, v;
        std::cin >> t >> v;
        if (t == 1) {
            state[v - 1] = !state[v - 1];
        } else if (t == 2) {
            // 查询节点v到所有红色节点的带权距离和
            vis.clear();
            vis.insert(v - 1);
            std::cout << ComputePathSum(n, v - 1, 0) << std::endl;
        }
    }
}

最优解法

使用树链剖分 + 线段树,以下是ai提供的代码...

C++ 复制代码
#include <iostream>
#include <vector>
#include <numeric>

using namespace std;

const int MAXN = 200005;
using ll = long long;

// Graph representation
vector<pair<int, int>> adj[MAXN];
bool is_red[MAXN];

// HLD variables
int parent[MAXN];
ll depth[MAXN];          // Weighted depth from root
int subtree_size[MAXN];
int heavy_child[MAXN];
int chain_head[MAXN];
int pos[MAXN];           // Position in the segment tree array
ll edge_weight_val[MAXN]; // edge_weight_val[u] is weight of edge (u, parent[u])
int timer;

// Global sums for the main formula
ll total_red_count = 0;
ll total_red_dist_sum = 0;

// Segment Tree
struct Node {
    ll sum_w;     // Sum of edge weights in the range
    ll sum_w_sz;  // Sum of (weight * sz) in the range
    ll lazy;      // Lazy tag for updates
} tree[4 * MAXN];

// DFS to calculate depths, parents, subtree sizes, and heavy children
void dfs1(int u, int p, ll d) {
    parent[u] = p;
    depth[u] = d;
    subtree_size[u] = 1;
    heavy_child[u] = 0;
    int max_sz = 0;

    for (auto& edge : adj[u]) {
        int v = edge.first;
        int w = edge.second;
        if (v == p) continue;
        edge_weight_val[v] = w;
        dfs1(v, u, d + w);
        subtree_size[u] += subtree_size[v];
        if (subtree_size[v] > max_sz) {
            max_sz = subtree_size[v];
            heavy_child[u] = v;
        }
    }
}

// DFS to perform decomposition and assign positions for the segment tree
void dfs2(int u, int head) {
    chain_head[u] = head;
    pos[u] = ++timer;

    if (heavy_child[u]) {
        dfs2(heavy_child[u], head);
    }

    for (auto& edge : adj[u]) {
        int v = edge.first;
        if (v != parent[u] && v != heavy_child[u]) {
            dfs2(v, v);
        }
    }
}

// --- Segment Tree Functions ---

void push_up(int p) {
    tree[p].sum_w = tree[2 * p].sum_w + tree[2 * p + 1].sum_w;
    tree[p].sum_w_sz = tree[2 * p].sum_w_sz + tree[2 * p + 1].sum_w_sz;
}

void push_down(int p, int L, int R) {
    if (tree[p].lazy == 0) return;

    int mid = L + (R - L) / 2;

    // Apply lazy tag to left child
    tree[2 * p].sum_w_sz += tree[p].lazy * tree[2 * p].sum_w;
    tree[2 * p].lazy += tree[p].lazy;

    // Apply lazy tag to right child
    tree[2 * p + 1].sum_w_sz += tree[p].lazy * tree[2 * p + 1].sum_w;
    tree[2 * p + 1].lazy += tree[p].lazy;

    tree[p].lazy = 0;
}

void build(int p, int L, int R, const vector<ll>& base_weights) {
    tree[p].lazy = 0;
    if (L == R) {
        tree[p].sum_w = base_weights[L];
        tree[p].sum_w_sz = 0; // sz is initially 0 for all nodes
        return;
    }
    int mid = L + (R - L) / 2;
    build(2 * p, L, mid, base_weights);
    build(2 * p + 1, mid + 1, R, base_weights);
    push_up(p);
}

void update(int p, int L, int R, int update_L, int update_R, int delta) {
    if (update_L <= L && R <= update_R) {
        tree[p].sum_w_sz += (ll)delta * tree[p].sum_w;
        tree[p].lazy += delta;
        return;
    }

    push_down(p, L, R);
    int mid = L + (R - L) / 2;
    if (update_L <= mid) {
        update(2 * p, L, mid, update_L, update_R, delta);
    }
    if (update_R > mid) {
        update(2 * p + 1, mid + 1, R, update_L, update_R, delta);
    }
    push_up(p);
}

ll query(int p, int L, int R, int query_L, int query_R) {
    if (query_L <= L && R <= query_R) {
        return tree[p].sum_w_sz;
    }

    push_down(p, L, R);
    int mid = L + (R - L) / 2;
    ll sum = 0;
    if (query_L <= mid) {
        sum += query(2 * p, L, mid, query_L, query_R);
    }
    if (query_R > mid) {
        sum += query(2 * p + 1, mid + 1, R, query_L, query_R);
    }
    return sum;
}

// --- HLD Path Functions ---

void path_update(int u, int n, int delta) {
    while (u) {
        update(1, 1, n, pos[chain_head[u]], pos[u], delta);
        u = parent[chain_head[u]];
    }
}

ll path_query(int u, int n) {
    ll sum = 0;
    while (u) {
        sum += query(1, 1, n, pos[chain_head[u]], pos[u]);
        u = parent[chain_head[u]];
    }
    return sum;
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    int n, q;
    cin >> n >> q;

    for (int i = 1; i <= n; ++i) {
        cin >> is_red[i];
    }

    for (int i = 0; i < n - 1; ++i) {
        int u, v, w;
        cin >> u >> v >> w;
        adj[u].push_back({ v, w });
        adj[v].push_back({ u, w });
    }

    // 1. Preprocessing for HLD
    dfs1(1, 0, 0);
    timer = 0;
    dfs2(1, 1);

    // 2. Build Segment Tree
    vector<ll> base_weights(n + 1);
    for (int i = 1; i <= n; ++i) {
        base_weights[pos[i]] = edge_weight_val[i];
    }
    build(1, 1, n, base_weights);

    // 3. Process initial red nodes
    for (int i = 1; i <= n; ++i) {
        if (is_red[i]) {
            total_red_count++;
            total_red_dist_sum += depth[i];
            path_update(i, n, 1);
        }
    }

    // 4. Handle queries
    for (int i = 0; i < q; ++i) {
        int type, v;
        cin >> type >> v;
        if (type == 1) {
            int delta = is_red[v] ? -1 : 1;
            is_red[v] = !is_red[v];

            total_red_count += delta;
            total_red_dist_sum += (ll)delta * depth[v];
            path_update(v, n, delta);
        }
        else {
            ll f_v = path_query(v, n);
            ll result = total_red_count * depth[v] + total_red_dist_sum - 2 * f_v;
            cout << result << "\n";
        }
    }

    return 0;
}
相关推荐
围巾哥萧尘24 分钟前
「电脑的故事」从电脑的故事谈用人的策略🧣
面试
shuououo29 分钟前
集成算法学习笔记
笔记·学习·算法
学历真的很重要35 分钟前
Eino 开源框架全景解析 - 以“大模型应用的搭积木指南”方式理解(一)
后端·语言模型·面试·golang·ai编程·eino
UrbanJazzerati1 小时前
Salesforce Flow 中集合操作的常见误解:值拷贝 vs. 引用传递
面试
呼啸长风1 小时前
漫谈散列函数
算法
NAGNIP1 小时前
彻底搞懂 RoPE:位置编码的新范式
算法
NAGNIP1 小时前
一文搞懂位置编码Positional Encoding
算法
丘山子1 小时前
分享链接格式不统一,rel="share-url" 提案试图解决这个问题
前端·面试·html
Ghost-Face2 小时前
关于模运算
算法
SunnyKriSmile2 小时前
指针实现数组的逆序存放并输出
c语言·算法·排序算法·数组逆序存放