这次竞赛还是有不错的题目的,树形背包问题平时见的不多,也帮助我巩固了下知识点。
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
#include
#include
#include
using namespace std;
int main() {
long int n;
std::cin>>n;
int res = 0;
while(n) {
res++;
n >>= 1;
}
cout<<res<<endl;
return 0;
}
给定数组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][j−v[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[j−v[i]])。
当然,题目的输入格式对c++选手是不友好的,虽然处理起来不难,多少也得多加几行代码才能处理输入。这题竞赛时过了八成用例,当时觉得是复杂度太大就做下一题了,看了下AC同学的题解也都是相同的方法求解,没有通过的两个用例倒是不太明白为啥没有过,相同代码提交到牛客上是可以通过的。
#include
#include
#include
#include
#include
#include
#include
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;
}
小明电脑空间满了,决定清空间。为了简化问题,小明列了他个人文件夹(/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][t−k]+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
#include
#include
#include
#include
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;
}
小明参加了个大型聚会。聚会上有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
#include
#include
#include
#include
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;
}