我明明记得比赛时最后一题一开始做错了,但我加了一条代码改对了,提交后马上(因为相同分数按用时排名)在另外界面点了交卷,但最后没记上分?
1.排查网络故障(小学奥数)
题目
A地跟B地的网络中间有n个节点(不包括A地和B地),相邻的两个节点是通过网线连接。
正常的情况下,A地和B地是可以连通的,有一天,A地和B地突然不连通了,已知只有一段网线出问题(两个相邻的节点)小明需要排查哪段网线出问题。
他的排查步骤是:
1。 选择某个中间节点
2。 在这个节点上判断跟A地B地是否连通,用来判断那一边出问题 请问小明最少要排查多少次,才能保证一定可以找到故障网线
题解
感觉像是小学奥数“找出唯一较轻的假币需要几次”那一类问题……
每次检查某个中间节点,若与A(或B)不连通,说明有问题的网线在节点和A(或B)之间。
每次尽可能检查中间点,从而每次将搜索范围缩小一半,这样在最坏情况下最好。
容易发现,答案就是n的二进制最高位在第几位。
代码
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
int solution(long int n) {
int result = 0;
while (n) {
result++;
n >>= 1;
}
return result;
}
int main() {
long int n;
std::cin >> n;
int result = solution(n);
std::cout << result << std::endl;
return 0;
}
2.零钱兑换(动态规划)
题目
给定数组arr,arr中所有的值都为正整数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个aim,代表要找的钱数,求组成aim的最少货币数。
如果无解,请返回-1.
数据范围:
数组大小满足 0 <= n <=10000 , 数组中每个数字都满足 0 < val <=10000,0 <= aim <=100000
要求:时间复杂度 O(n×aim) ,空间复杂度 O(aim)。
题解
动态规划
dp[面值]=最小张数或-1(无答案,其实简单起见也可以设成足够大的特殊值)
初始dp[0]=0, dp[i]=-1
dp[aim] = min(1<=i<=n, dp[aim-val[i]]!=-1){dp[aim-val[i]]+1}
因为“每种面值的货币可以使用任意张”,所以更新时aim从小到大更新,特殊处理-1的情况。
代码
package main
import "fmt"
import "strings"
var arr []int
var dp []int
func str2int(s string) int {
var x = 0
for _, ch := range []byte(s) {
x = x*10 + int(ch-'0')
}
return x
}
func main() {
var str string
fmt.Scan(&str)
str = str[1:]
str2 := strings.Split(str, "]")
arrS := strings.Split(str2[0], ",")
aimS := str2[1][1:]
//fmt.Println(arrS, aimS)
for _, as := range arrS {
arr = append(arr, str2int(as))
}
aim := str2int(aimS)
dp = make([]int, aim+1)
for i := range dp {
dp[i] = -1
}
dp[0] = 0
for i := range arr {
for j := 0; j <= aim; j++ {
if j < arr[i] || dp[j-arr[i]] == -1 {
continue
}
if dp[j] == -1 || (dp[j-arr[i]] != -1 && dp[j-arr[i]]+1 < dp[j]) {
dp[j] = dp[j-arr[i]] + 1
}
}
}
fmt.Println(dp[aim])
}
3.清理磁盘空间(树形dp)
题目
小明电脑空间满了,决定清空间。
为了简化问题,小明列了他个人文件夹(/data)中所有末级文件路径和大小,挑选出总大小为 m 的删除方案,求所有删除方案中,删除操作次数最小是多少。
一次删除操作:删除文件或者删除文件夹。如果删除文件夹,那么该文件夹包含的文件都将被删除。
文件夹的大小:文件夹中所有末级文件大小之和。
笔者注:无答案时输出-1
题解
把路径按/切割,建立目录树,将每个文件加到目录树中,同时增加沿途文件夹的大小。
树形dp:
dp[目录树节点号[被删总文件大小]=最小删除次数
对于每个子树(每个文件夹),可以整个删除,或执行以子节点为根子树的中的多个删除:
当sum{size} == size[root],有dp[root][sum{sizeii}] = 1
否则dp[root][sum{sizeii}] = min{dp[son1][size1], ..., dp[soni][sizei],...}
代码
这个实现效率不高,因为叶子节点dp[leaf][]只有一个不是-1,整个dp[][]中-1预计也很多,估计用C++的map会好一些。
(因为我不熟悉C++的字符串操作。golang的map是无序的,相当于C++的unordered_map。当时想可能有顺序相关的优化,所以用的数组)
package main
import (
"fmt"
"strings"
)
type Node struct {
str []string
sz int
}
var nodes []Node
func str2int(s string) int {
var x = 0
for _, ch := range []byte(s) {
x = x*10 + int(ch-'0')
}
return x
}
type Tree struct {
id int
mp map[string]*Tree
sz int
}
var root Tree
var tot int
func add(rt *Tree, node Node, step int) {
rt.sz += node.sz
if rt.mp == nil {
rt.mp = make(map[string]*Tree)
}
if len(node.str) == step {
return
}
if rt.mp[node.str[step]] == nil {
tree := new(Tree)
tot++
tree.id = tot
rt.mp[node.str[step]] = tree
}
add(rt.mp[node.str[step]], node, step+1)
}
var dp [][]int
var n, m int
func main() {
var temp_arr [2]int
for i := 0; i < 2; i++ {
fmt.Scan(&temp_arr[i])
}
n = temp_arr[0]
m = temp_arr[1]
matrix := make([][2]string, n)
for i := 0; i < n; i++ {
for j := 0; j < 2; j++ {
fmt.Scan(&matrix[i][j])
}
node := Node{}
node.str = strings.Split(matrix[i][0], "/")
node.sz = str2int(matrix[i][1])
nodes = append(nodes, node)
add(&root, node, 0)
}
dp = make([][]int, tot+1)
for i := 0; i <= tot; i++ {
dp[i] = make([]int, m+1)
for j := range dp[i] {
dp[i][j] = -1
}
dp[i][0] = 0
}
dfs(&root)
fmt.Println(dp[root.id][m])
//fmt.Println(dp)
}
func dfs(rt *Tree) {
//fmt.Println(rt.id)
if rt.sz <= m {
dp[rt.id][rt.sz] = 1
}
if rt.mp == nil {
// dp[rt.id][rt.sz] = 1
return
}
for _, v := range rt.mp {
dfs(v)
for aim := m; aim >= 1; aim-- {
// 注:叶子节点显然-1数量特别多,应该用map保存更好
// 没有 son <= v.sz 只能得 90%
for son := 1; son <= aim && son <= v.sz; son++ {
if dp[v.id][son] == -1 {
continue
}
if dp[rt.id][aim-son] != -1 &&
(dp[rt.id][aim] == -1 || dp[rt.id][aim] > dp[rt.id][aim-son]+dp[v.id][son]) {
dp[rt.id][aim] = dp[rt.id][aim-son] + dp[v.id][son]
}
}
}
}
}
4.交际圈(无向图连通块个数)
题目
小明参加了个大型聚会。聚会上有n个人参加,我们将他们编号为1…n,有些人已经互相认识了,有些人还不认识。
聚会开始后,假设A跟B认识,A会给所有他认识的人介绍B,原先跟A认识,但不认识B的人,都会在此时,跟B互相认识。
当所有人都把自己认识的人介绍一遍后,此时n个人就会形成k个交际圈,同一个交际圈中,两两互相认识,不同的交际圈之间,互相不认识
问题:当所有人都把自己认识的人介绍一遍后,形成了多少个交际圈.
题解
每一对互相认识关系让两拨人互相认识,抽象就是一条无向边让无向图中两个连通块变成一个连通块。
对于无向图连通块,可以从任何一点开始遍历到所有点
建无向图遍历,标记遍历过的点,总遍历次数就是连通块个数,就是答案。
答案
我明明记得比赛时最后一题一开始做错了,但我加了一条代码改对了,提交后马上(因为相同分数按用时排名)在另外界面点了交卷,但最后没记上分?
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
using namespace std;
vector<int> e[100010];
bool vis[101000];
void dfs(int u) {
vis[u] = true;
for (int i = 0; i < (int) e[u].size(); i++) {
if (vis[e[u][i]])continue;
dfs(e[u][i]);
}
}
int main() {
int n;
int m;
std::cin >> n;
std::cin >> m;
for (int i = 1; i <= m; i++) {
int x, y;
cin >> x >> y;
e[x].push_back(y);
e[y].push_back(x);
}
int ans = 0;
for (int i = 1; i <= n; i++) {
if (vis[i]) continue; // 我明明记得比赛时一开始错了,但我加了这条代码改对了,但最后没记上分?
++ans;
dfs(i);
}
cout << ans;
return 0;
}
文章提供了四道编程竞赛题目,包括网络故障排查的奥数问题、零钱兑换的动态规划解决方案、清理磁盘空间的树形dp策略以及交际圈的无向图连通块计数。每道题均附带题解思路和C++或Golang的代码实现。
1310

被折叠的 条评论
为什么被折叠?



