2023牛客寒假算法基础集训营1 个人题解(ACDHKL)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2023牛客寒假算法基础集训营1 个人题解(ACDHKL)相关的知识,希望对你有一定的参考价值。

A.World Final? World Cup! (I)

题意:

给10场比赛的点球输赢情况,奇数为A队点球,偶数为B队点球

思路:

用两个变量x,y来分别存A队当前赢的场次和B队当前赢的场次

然后就就扫一遍01字符串,如果为当前为奇数且为\'1\',x++,当前为偶数且为\'1\',y++

这里最主要的是判断是否当前可以直接终结比赛(后面比赛打不打没有意义)

相当于就算后面的场次全输且对面的队伍全赢也可以赢的局面就可以终结比赛

每次都判断一下是否A队当前赢的场次 > B队赢的场次+后面B队剩余的场次

或者B队当前赢的场次 > A队赢的场次+后面A队剩余的场次

这里算后面的场次的时候我WA了一发

因为我默认都搞成向上取整了(

正确的方法如下:

算A队后面的场次:$(n - i) / 2$

算B队后面的场次:$(n - i + 1) / 2$

最后遍历完了还算不出来就输出-1

代码:

void solve()

	string s1;
	cin >> s1;
	n = s1.size();
	int x = 0,y = 0;
	s1 = " " + s1;
	for(int i = 1;i <= n;i++)
	
		if((i&1)&&(s1[i] == \'1\')) x++;
		else if(s1[i] == \'1\') y++;
		if(x > y + (n - i + 1) / 2||y > x + (n - i) / 2)
		
			cout << i << endl;
			return;
		
	
	cout << -1 << endl;

总结:

在向上取整的时候一定要考虑奇偶,大概率会不一样(

C.现在是,学术时间 (I)

题意:

给n个数字随意分配到n个位置上

定义每一个位置的价值为(位置上的数大小 > 位置上的数字数量)的最大数量

求经过分配后的每个位置价值之和的最大值

思路:

最开始的思路是贪心

每个数的承载量就是数字的大小

比如一个3就可以和另外两个3放在一个位置上有一个为3的贡献

然后就用map处理一下

然后就喜提WA

原因是没去考虑最大贡献,最大贡献其实就是n

因为把数字叠在一起并不能增大总值,反而有可能降低总值

所以最优策略就是不分配,然后算一下>0的值即可

代码:

void solve()

	int ans = 0;
	cin >> n;
	for(int i = 1;i <= n;i++)
	
		cin >> temp;
		if(temp >= 1) ans++;
	
	cout << ans << endl;

 总结:

贪心之前想想哪样会更优,算算理论最优值是多少

前面用map做法就是典型的负面贪心

D.现在是,学术时间 (II)

题意:

给一个矩形由$(a,b)$和$(0,0)$构成

再给一个点$(x,y)$,要你找个点和这个点再构成一个矩形

要求这两个矩形的交集面积 / 并集面积最大

思路:

肯定是交集面积越大,并集面积越小越好

然后根据两个点的大小关系我觉得可以分为以下四种情况来讨论

1.x >= a&&y >= b

这种情况很好判断,无脑选原点就完事了

交集为:$a * b$

并集为:$x * y$

2.x <= a&&y <= b

点在第一个矩形里面

显然选第一个矩形的四个点分别讨论一下取交集面积最大的即可

那么交集面积为:$max(x * y,(a - x) * y,x * (b - y),(a - x) * (b - y))$

并集面积为:$a * b$

3.x >= a&&y <= b

这种情况就比较容易错

因为并集不是$x * y$或者$a * b$了,而且这种情况里又分为两种情况,每个情况的并集不一样

情况1:以原点和(x,y)画第二个矩形

交集:$a * y$

并集:$a * b + (x - a) * y$

情况2:以(0,b)和(x,y)画第二个矩形

交集:$a * (b - y)$

并集:$a * b + (x - a) * (b - y)$

4.x <= a&&y >= b

与第三种情况对称即可,详细见代码

代码:

void solve()

	cout<<setiosflags(ios::fixed)<<setprecision(9);
	double ans = 0;
	int a,b,x,y,op,uu;
	cin >> a >> b >> x >> y;
	if(x >= a&&y >= b)
	
		op = x * y,uu = a * b;
		ans = max(ans,uu * 1.0 / (op * 1.0));
	
	else if(x <= a&&y <= b)
	
		op = a * b;
		uu = max(x * y,(a - x) * y,x * (b - y),(a - x) * (b - y));
		ans = max(ans,uu * 1.0 / (op * 1.0));
	
	else if(x >= a&&y <= b) 
	
		op = a * b + (x - a) * y;
		uu = a * y;
		ans = max(ans,uu * 1.0 / (op * 1.0));
		op = a * b + (x - a) * (b - y);
		uu = a * (b - y);
		ans = max(ans,uu * 1.0 / (op * 1.0));
	 
	else if(x <= a&&y >= b)
	
		op = a * b + (y - b) * x;
		uu = b * x;
		ans = max(ans,uu * 1.0 / (op * 1.0));
		op = a * b + (y - b) * (a - x);
		uu = b * (a - x);
		ans = max(ans,uu * 1.0 / (op * 1.0));
	
	cout << ans << endl;

总结:

这种分类讨论题一定要画图,而且画图不要太潦草(

之前画图画潦草了迟迟分类不出来,最后重新画了一张就完事了

G.鸡格线

题意:

给一个长为n的数组,每次有两种操作

1:对l到r的数字进行$f(x) = round(10 * \\sqrtx)$

2:求所有数字之和

思路:

第一眼看起来是线段树,也确实能用线段树写(

但是这题还用不着线段树(

打个表看下规律,发现所有>0and<=99的数都会向99靠拢

所有>99的数都会向100靠拢

最大的数操作次数不超过20次就会变成目标数字

那么我们按照题意模拟即可

但是我们不知道哪些数是已经被操作完了的,所以这里有个小技巧

就是用set来存没有完成的数字

然后每次二分找比l大的数去操作$min(20,k)$次,并且更新相关数据即可

注意这里要存下标,不能存数字,因为是对l,r进行操作

每次操作完一个数字看是否还需要操作,不需要就删去这个数字

代码:

int f(int x)

	return round(10 * sqrt(x));


void solve()

	int sum = 0;
	set<int> s;
	cin >> n >> m;
	vector<int> a(n + 1);
	for(int i = 1;i <= n;i++)
	
		cin >> a[i];
		sum += a[i];
		if(a[i] != f(a[i])) s.insert(i);
	
	int l,r,k;
	while(m--)
	
		cin >> temp;
		if(temp == 1)
		
			cin >> l >> r >> k;
			while(1)
			
				auto it = s.lower_bound(l);
				int x = *it;
				if(it == s.end()||x > r) break;
				for(int i = 1;i <= min(20ll,k);i++)
				
					sum += f(a[x]) - a[x];
					a[x] = f(a[x]);
				
				if(a[x] == f(a[x])) s.erase(it);
				l = x + 1;
			
		
		else cout << sum << endl;
	

总结:

看到有这种函数就尝试一下打表,一般都能出规律

对于一些数字只操作有限次,就可以用set存下标配合二分去寻找下一个有效值

H.本题主要考察了DFS

题意:

给$n * n - 1$块积木,然后问缺了哪一块,输出缺的那一块的形状数字

思路:

这里被套了,一看题目有dfs,然后长得也很像个dfs

写dfs写到一半突然发现好像不用dfs

因为最后题目问的缺的那一块并没有具体问是啥形状

只要知道有几个凹的和几个凸的的就行了

那这题就很简单了,统计一下出现的1和2的个数

因为1和2肯定是一一对应的,所以看哪个缺就补哪个就完事了

代码:

void solve()

	int x = 0,y = 0,z = 0;
	string s1;
	cin >> n;
	char c;
	for(int i = 1;i <= n * n - 1;i++)
	
		for(int j = 1;j <= 4;j++)
		
			cin >> c;
			if(c == \'0\') x++;
			else if(c == \'1\') y++;
			else z++;
		
	
	if(y < z) cout << 10 - abs(y - z) << endl;
	else if(y == z) cout << 10 << endl;
	else if(y > z) cout << 10 + abs(y - z) << endl;
	// cout << x << " " << y << " " << z << endl;

总结:

一定要先看题目问的啥,不要脑补题目条件(

还有看到题目有很明显的知识点大概率是误导(

K.本题主要考察了dp

题意:

要你构造一个长度为n的01串,并且其中1的数量为m

问构造出这样的串中所有长度为3的子区间中1的个数>0的个数的子区间数最少有几个

思路:

由于不想让1的个数>0的个数,考虑用2个0去匹配1个1

很容易这样构造:100100100100....1111

就是先构造上面的100100100这样看剩下几个0和几个1

把1全部放在最后面就是最小值

答案为1的个数-1

但是这样要注意边界问题

如果1的个数为0就会 出现负数,所以要对0取max

另外如果整个串全是1,输出就是n - 1,但是答案是$n - 2$

所以要再对$n - 2$取个min

代码:

void solve()

	cin >> n >> m;
	int x = n - m,y = m;
	// cout << x << " " << y << endl;
	while(x >= 2&&y >= 1)
	
		x -= 2;
		y--;
	
	cout << min(max(y - 1,0ll),n - 2) << endl;

总结:

有时候猜出构造方法了还需要注意边界问题,最好交题前测测极限数据

M.本题主要考察了找规律

 

题意:

有n个人,m个物品来分配,每次分配得到的贡献值为给这个人分配的物品数 / 未给这个人分配前的物品总数

问获得的贡献值最大为多少

思路:

看范围,好了是dp

状态表示为:前i个人给了j个物品的最大贡献值

状态转移:i - 1个人就给了j - k个物品 + 本次贡献值

就是$dp[i][j] = dp[ i - 1][j - k] + k / m - j + k$

枚举$[0,k]$次即可

然后就出来了

代码:

void solve()

	cout<<setiosflags(ios::fixed)<<setprecision(9);
	cin >> n >> m;
	for(int i = 1;i <= n;i++)
	
		for(int j = 1;j <= m;j++)
		
			for(int k = 0;k <= j;k++)
			
				dp[i][j] = max(dp[i][j],dp[i - 1][j - k] + k * 1.0 / (m - j + k));
			
		
	
	cout << dp[n][m] << endl;

总结:

最基础的概率dp

dp还是练的太少太少了,平时遇到dp就跳过((

找个时间专项训练一下(

 

以上是关于2023牛客寒假算法基础集训营1 个人题解(ACDHKL)的主要内容,如果未能解决你的问题,请参考以下文章

2022牛客寒假算法基础集训营1题解AHFIDBKG

2023牛客寒假算法基础集训营

D宿命之间的对决2023牛客寒假算法基础集训营3

C忽远忽近的距离2023牛客寒假算法基础集训营3

2022牛客寒假算法基础集训营6题解 ABCDEFGHIJ

2022牛客寒假算法基础集训营3全部题解