CSDN竞赛23期题解

总结

这次竞赛还是有不错的题目的,树形背包问题平时见的不多,也帮助我巩固了下知识点。

题目列表

1.排查网络故障

题目描述

A地跟B地的网络中间有n个节点(不包括A地和B地),相邻的两个节点是通过网线连接。正常的情况下,A地和B地是可以连通的,有一天,A地和B地突然不连通了,已知只有一段网线出问题(两个相邻的节点)小明需要排查哪段网线出问题。他的排查步骤是:
1。 选择某个中间节点
2。 在这个节点上判断跟A地B地是否连通,用来判断那一边出问题

请问小明最少要排查多少次,才能保证一定可以找到故障网线

输入描述:

一个正整数 n (n <= 10^18),表示A地和B地之间的节点数

输出描述:

输出一个数字,代表保证一定可以找到故障网线的前提下,小明最少要排查多少次

输入样例:

2

输出样例:

2

分析

本题要求的是二分的次数,枚举下前几个数找下规律。中间有1个数需要排查1次;2个数需要排查2次,3个数需要排查2次,4个数需要排查3次,可以发现需要排查的次数等于中间节点个数的字长,输出下即可。

代码

#include <iostream>
#include <string>
#include <sstream>
#include <vector>
using namespace std;
int main() {
	long int n;
	std::cin>>n;
	int res = 0;
	while(n) {
		res++;
		n >>= 1;
	}
	cout<<res<<endl;
	return 0;
}

2.零钱兑换

题目描述

给定数组arr,arr中所有的值都为正整数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个aim,代表要找的钱数,求组成aim的最少货币数。
如果无解,请返回-1.

数据范围:数组大小满足 0 <= n <=10000 , 数组中每个数字都满足 0 < val <=10000,0 <= aim <=100000

要求:时间复杂度 O(n×aim) ,空间复杂度 O(aim)。

输入描述:

输出描述:

输入样例:

[5,2,3],20

输出样例:

4

分析

这题也挺坑的,本来是简单的背包问题。牛客网是有评测原题的NC126 兑换零钱(一),但是本题的数据范围显然有问题,要求时间复杂度是O(n×aim),但是n乘上aim数量级是 1 0 9 10^9 109的,显然会TLE。

状态转移方程是 f [ i ] [ j ] = m i n ( f [ i ] [ j ] , f [ i ] [ j − v [ i ] ] ) f[i][j] = min(f[i][j], f[i][j-v[i]]) f[i][j]=min(f[i][j],f[i][jv[i]]),使用滚动数组后可以得到 f [ j ] = m i n ( f [ j ] , f [ j − v [ i ] ] ) f[j]=min(f[j],f[j-v[i]]) f[j]=min(f[j],f[jv[i]])

当然,题目的输入格式对c++选手是不友好的,虽然处理起来不难,多少也得多加几行代码才能处理输入。这题竞赛时过了八成用例,当时觉得是复杂度太大就做下一题了,看了下AC同学的题解也都是相同的方法求解,没有通过的两个用例倒是不太明白为啥没有过,相同代码提交到牛客上是可以通过的。

代码

#include <iostream>
#include <string>
#include <sstream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
int f[100005];
int a[10005];
int main() {
	std::string str1;
	char c = getchar();
	int n = 0;
	int x, m;
	while(c != ']') {
		cin>>x;
		a[n++] = x;
		c = getchar();
	}
	getchar();
	cin>>m;
	memset(f,0x3f,sizeof f);
	f[0] = 0;
	for(int i = 0; i < n; i++) {
		for (int j = 0; j <= m; j++) {
			if (j >= a[i]) f[j] = min(f[j], f[j - a[i]] + 1);
		}
	}
	if (f[m] == 0x3f3f3f3f) cout<<-1<<endl;
	else cout<<f[m]<<endl;
	return 0;
}

3.清理磁盘空间

题目描述

小明电脑空间满了,决定清空间。为了简化问题,小明列了他个人文件夹(/data)中所有末级文件路径和大小,挑选出总大小为 m 的删除方案,求所有删除方案中,删除操作次数最小是多少。

一次删除操作:删除文件或者删除文件夹。如果删除文件夹,那么该文件夹包含的文件都将被删除。
文件夹的大小:文件夹中所有末级文件大小之和

输入描述:

第一行输入 n (n <= 1000)和 m(m <= 1000),表示文件数量,和需要删除的大小

接下去有 n 行,每一行都是一个文件绝对路径(路径长度小于 100),和这个文件的大小(小于 1000)

输出描述:

输出所有删除方案中,删除操作次数最小是多少。如果找不到恰好删除的大小为 m 的方案,则打印 -1

输入样例:

6 10
/data/movie/a.mp4 5
/data/movie/b.mp4 3
/data/movie/c.mp4 2
/data/movie/d.mp4 4
/data/picture/a.jpg 4
/data/picture/b.jpg 1

输出样例:

2

分析

本题是这次竞赛的压轴题,虽然在树形背包里面不属于难题,但是很久没做树形背包的问题还是很难想到的。树形背包问题的模板题可以参考AcWing286 选课。在选课问题的基础上再来看这题就不难解决了。

首先需要根据输入的字符串建树,以"/data/movie/a.mp4"为例,遍历一遍,将"/data" “/data/movie” "/data/movie/a.mp4"都存到hash表里映射成整数,建立父子节点关系。之后就是在dfs的过程中进行状态转移了。

状态表示: f [ u ] [ t ] f[u][t] f[u][t]表示以 u u u为根的子树中删除总大小为 t t t的节点需要删除操作的最小次数,要求一个节点不能和它的祖先节点同时被删除。状态转移方程为: f [ u ] [ t ] = m i n ( f [ u ] [ t ] , f [ u ] [ t − k ] + f [ j ] [ k ] ) f[u][t] = min(f[u][t],f[u][t-k]+f[j][k]) f[u][t]=min(f[u][t],f[u][tk]+f[j][k]),这样遍历完 u u u的孩子节点后相当于求出了留下 u u u删除 u u u的若干个后代的状态,然后再更新下只删除 u u u的状态 f [ u ] [ w [ u ] ] = 1 f[u][w[u]]=1 f[u][w[u]]=1,表示删除 u u u目录后删除了 w [ u ] w[u] w[u]大小的文件需要的删除操作次数是1。

树形背包的具体推导和优化可以参考前面的选课问题的题解,这里仅仅说下这个问题的不同之处,本题的最优化方案是最小值,所以初始状态应该设置为INF,同时设置 f [ i ] [ 0 ] = 0 f[i][0]=0 f[i][0]=0表示不删除文件的操作次数是0。

代码

#include <iostream>
#include <string>
#include <cstring>
#include <algorithm>
#include <unordered_map>
using namespace std;
unordered_map<string,int> m;
const int M = 1005, N = 400005;
int f[N][M];
int n,d,cnt = 0;
int idx,e[N],ne[N],w[N],h[N];
void add(int a, int b) {
	e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void split(string &s,int q) {
	int n = s.size();
	string t;
	int last = -1;
	for(int i = 1;i <= n;i++) {
		if (s[i] == '/' || i == n) {
			if (!m.count(t))	m[t] = cnt++;
			int j = m[t];
			w[j] += q;
			if (last != -1)	add(last, j);
			last = m[t];
		} else {
			t += s[i];
		}
	}
}
int dfs(int u) {
	int s = w[u];
	for(int i = h[u];~i;i = ne[i]) {
		int j = e[i];
		int q = dfs(j);
		s += q;
		int tot = min(s + 1, d);
		for (int t = tot; t >= 0;t--) {
			int r = min(t,q);
			for (int k = 0;k <= r;k++) {
				f[u][t] = min(f[u][t], f[u][t-k] + f[j][k]);
			}
		}
	}
	if (w[u] <= d)	f[u][w[u]] = 1;
	return s;
}
int main() {
	cin>>n>>d;
	string s;
	int x;
	memset(h,-1,sizeof h);
	memset(f,0x3f,sizeof f);
	for (int i = 0;i < n;i++) {
		cin>>s>>x;
		split(s,x);
	}
	int u = m["data"];
	for(int i = 0;i < cnt;i++)	f[i][0] = 0;
	dfs(u);
	cout<<f[u][d]<<endl;
	return 0;
}

4.交际圈

题目描述

小明参加了个大型聚会。聚会上有n个人参加,我们将他们编号为1…n,有些人已经互相认识了,有些人还不认识。聚会开始后,假设A跟B认识,A会给所有他认识的人介绍B,原先跟A认识,但不认识B的人,都会在此时,跟B互相认识。当所有人都把自己认识的人介绍一遍后,此时n个人就会形成k个交际圈,同一个交际圈中,两两互相认识,不同的交际圈之间,互相不认识
问题:当所有人都把自己认识的人介绍一遍后,形成了多少个交际圈

输入描述:

第1行包含两个数字n(n <= 100000), m(m <= 100000),n表示参加聚会的人数,m表示聚会前,有多少对人已经互相认识了
第2行到第m+1行,每一行包含两个数字,a和b(a != b, 1 <= a, b <= n),代表的是聚会前,a和b已经互相认识

输出描述:

输出一个数字,表示形成的交际圈的个数

输入样例:

3 3
1 2
1 3
2 3

输出样例:

1

分析

并查集裸题,建立集合后统计下集合数即可。

代码

#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <set>
using namespace std;
const int N = 100005;
int fa[N];
set<int> s;
int find(int x) {
	if (fa[x] != x) fa[x] = find(fa[x]);
	return fa[x];
}
int main() {
	int n;
	int m;
	cin>>n>>m;
	for(int i = 1; i <= n; i++) fa[i] = i;
	int a, b;
	for (int i = 0; i < m; i++) {
		cin>>a>>b;
		fa[find(a)] = find(b);
	}
	for (int i = 1; i <= n; i++) {
		int t = find(i);
		s.insert(t);
	}
	int res = s.size();
	cout<<res<<endl;
	return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值