2023牛客寒假算法基础集训营2(补题ing)

A(easy)

签到题写了半个多小时。。。
题目描述:
已知一个数n,和区间[L1, R1],[L2, R2],求所有满足L1 <= a <= R1,L2 <= b <= R2,使得a+b=n的所有的解的选法。对于两种选法,若a,b有任意一个数不同,则算作不同的选法。
输入描述:
对于每组测试数据:
第一行包含一个整数n(1 <= n <= 2·10^5)。
第二行包含两个整数L1,R1(1 <= L1 <= R1 <= 10^5)。
第三行包含两个整数L2, R2(1 <= L2 <= R2 <= 10^5)。

分析:

  1. 看到区间的数据范围就知道不能暴力两重循环枚举a,b;
  2. 然后发现n的范围是合规的复杂度(于是可以想到将a+b=n变换成b=n-a),这样最多就只用枚举n次(其实也想了半天才发现)

Right idea:枚举a=L1到R1,b=n-a,再判断b是否在区间[L2, R2]中,如果是,答案+1;

我的ac code比较混乱,没出题人正解好看。

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
int main() {
	int t;
	cin >> t;
	while(t--){
		int n;
		cin >> n;
		int cnt = 0;
		long long l1, r1, l2, r2;
		cin >> l1 >> r1 >> l2 >> r2;
		
		int ans1 = l1;
		while(ans1 <= r1){
			int ans2 = n - ans1;
			if(ans2 >= l2 && ans2 <= r2){
				cnt++;
			}
			ans1++;
		}
	
		cout << cnt << endl;
	}
	return 0;
}

正解code:

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;

void solve() {
	int n;
	cin >> n;
	int l1, r1, l2, r2;
	cin >> l1 >> r1 >> l2 >> r2;
	int cnt = 0;
	for (int a = l1; a <= r1; a++) {
		int b = n - a;
		if (b >= l2 && b <= r2) {
			cnt++;
		}
	}
	cout << cnt << '\n';
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	int t;
	cin >> t;
	while (t--) {
		solve();
	}
	return 0;
}

B(medium)

心路历程:
A题是发现n的范围内复杂度够,到B题,n的数据也大了。。。
输入描述:
对于每组测试数据:
第一行包含一个整数n(1 <= n <= 2·10^9)。
第二行包含两个整数L1,R1(1 <= L1 <= R1 <= 10^9)。
第三行包含两个整数L2, R2(1 <= L2 <= R2 <= 10^9)。

思路:
根据a的取值范围[L1, R1],我们求出能满足a+b=n的b的范围是[n-R1, n - L1],(注意不是[n-L1, n - R1], 题解这里写错了!),但是合法的b的范围是[L2, R2].所以答案是两个区间取交集后的区间长度。
区间取交集的区间长度计算公式:
两个区间[a, b][c, d]取交集的区间长度为:len = max(0, min(d,b) - max(a,c) + 1);

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;

void solve() {
	int n;
	cin >> n;
	int l1, r1, l2, r2;
	cin >> l1 >> r1 >> l2 >> r2;

	int lb = n - r1, rb = n - l1;
  //  cout << lb << " " << rb << '\n';	//一开始直接按照题解写满足b = n - a的区间,一直错,好在最后发现了~~(题解不是万能,还是得靠自己想)~~
	int ans = max(0, (min(rb, r2) - max(lb, l2) + 1));
	cout << ans << '\n';
}



int main() {

	ios::sync_with_stdio(false);
	cin.tie(0);
	int t;
	cin >> t;
	while (t--) {
		solve();
	}
	return 0;
}

J(easy-medium)

题目描述:
长度为n的序列a。定义MxAb(i,j)=max(|\(a_i\) - \(a_j\)),|\(a_i\) + \(a_j\)|);
求对于所有的i,j(1<=i,j<=n),MxAb(i, j)的和为多少
\(\sum \limits_{i=1}^{n}\sum \limits_{j=1}^{n}\)MxAb\((i,j)\)
=\(\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}(|a_i|+|a_j|)\)
=\(\sum\limits_{i=1}^{n}(n·|a_i|+\sum\limits_{j=1}^{n}|a_j|)\)
=\(\sum\limits_{i=1}^{n}n·|a_i|+n·\sum\limits_{j=1}^{n}|a_j|\)
=\(n·\sum\limits_{i=1}^{n}|a_i|+n·\sum\limits_{i=1}^{n}|a_i|\)
=\(2·n·\sum\limits_{i=1}^{n}|a_i|\)

心路历程:
读完题目,模拟的暴力做法就知道了,但是一看范围,铁定tle,于是想方法优化,结果我那点小优化约等于没有优化。给样例解释仅限于理解题解,按样例模拟的做法会tle,所以优化优化再优化。

思路:
看到带有绝对值的式子,一般先展开绝对值。对a[i]和a[j]的正负进行讨论:
a[i] < 0, a[j] < 0 时,max(|a[i] − a[j]|, |a[i] + a[j]|) = |a[i] + a[j]| = |a[i]| + |a[j]|
a[i] < 0, a[j] > 0 时,max(|a[i] − a[j]|, |a[i] + a[j]|) = |a[i] − a[j]| = |a[i]| + |a[j]|
a[i] > 0, a[j] < 0 时,max(|a[i] − a[j]|, |a[i] + a[j]|) = |a[i] − a[j]| = |a[i]| + |a[j]|
a[i] > 0, a[j] > 0 时,max(|a[i] − a[j]|, |a[i] + a[j]|) = |a[i] + a[j]| = |a[i]| + |a[j]|
所以max(|a[i]-a[j]|, |a[i]+a[j]|)=|a[i]|+|a[j]|
那么答案
\(\sum \limits_{i=1}^{n}\sum \limits_{j=1}^{n}\)MxAb\((i,j)\)
=\(\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}(|a_i|+|a_j|)\)
=\(\sum\limits_{i=1}^{n}(n·|a_i|+\sum\limits_{j=1}^{n}|a_j|)\)
=\(\sum\limits_{i=1}^{n}n·|a_i|+n·\sum\limits_{j=1}^{n}|a_j|\)
=\(n·\sum\limits_{i=1}^{n}|a_i|+n·\sum\limits_{i=1}^{n}|a_i|\)
=\(2·n·\sum\limits_{i=1}^{n}|a_i|\)

rk1大佬jiangly的写法

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
int a[N];
void solve() {
	int n;
	cin >> n;
	LL ans = 0;
	for(int i = 1; i <= n; i++){
		int a;
		cin >> a;
		ans += abs(a);
	}
	ans *= n * 2;
	cout << ans << "\n";
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	int t;
	cin >> t;
	while (t--) {
		solve();
	}
	return 0;
}

H(easy-medium)

题目描述:
有一个长度为n的序列a,把这个序列划分成k个非空子序列,定义序列的值为这个序列中只出现一次的数字的个数。对于k=1,2...n,求把这个序列a划分成k个非空子序列后,所有子序列的值的和最大是多少。(注意:子序列不一定是连续的)
输入描述:
对于每组测试数据:
第一行包含一个整数n(1\(\leq\)n$\leq$10^5 )
第二行包含n个整数 \(a_1,a_2,...a_n(1\leq a_i\leq10^5)\)
输出描述:
对于每组数据,输出n个整数,第i个整数表示k=i时的答案。

思路:
可以发现每种数字的贡献可以分开计算,我们按照每种数字出现的次数进行讨论。
假设数字x出现的次数为\(cnt_x\):
如果\(cnt_x\)\(\leq\)k,我们可以贪心地将每个x都分到某个子序列中,使得每个子序列要么只包含1个x,要么不包含x。所以此时数字x的贡献为cnt.
如果\(cnt_x\)\(\geq\)k,我们按照上面的方法分配完k个x,多出来的x必须分配到某一个子序列中,导致那个子序列中数字x没有贡献。所以此时数字x的贡献为k-1.
答案就是每种数字的贡献求和。
现在题目求k=1...n的答案,我们对cnt从小到大排序,枚举k=1,...n.答案为\(cnt_x\)\(\leq\)k的\(cnt_x\)求和,加上\(cnt_x\)>k,的\(cnt_x\)的个数乘上(k-1)。

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
int a[N], cnt[N];	//cnt数组存每个数出现的个数 
int n;
void solve() {
		cin >> n;
		
		//注意多测清空 
		memset(cnt, 0, sizeof(cnt));
		
		for(int i = 1; i <= n; i++){
			cin >> a[i];
			cnt[a[i]]++;
		}
		
		vector<int> res;	//把每个数出现的次数存入vector<int> res中 
		for(int i = 1; i <= N - 10; i++){
			if(cnt[i]) res.push_back(cnt[i]);
		}
		
		sort(res.begin(), res.end());
		
		int ans = 0;
		for(int k = 1,i = 0; k <= n; k++){
			while(i < res.size() && res[i] <= k){
				ans += res[i];
				i++;
			}
			if(i == n) printf("%d\n", ans);
			else printf("%d\n",(res.size() - i) * (k - 1) + ans);
		}
}


int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	int t;
	cin >> t;
	while (t--) {
		solve();
	}
	return 0;
}

D(medium)

题目描述:
有n个节点的有根能量树,根为1.最开始,树上每个节点的能量都是0。现在有n个能量球,第i个能量球拥有\(v_i\)能量。现在把这n个能量球分别放置在能量树的每个节点上,使能量树的每个节点都恰好有一个能量球。
进行n次操作,每次只能放置一个能量球。每一次操作,选择一个能量球,放置在一个没有能量球的能量树节点x上。之后,获得以x为根的子树中所有能量球的能量(包括节点x的能量球能量)。求放置完所有能量球后,可能获得的总能量最多是多少。

输入描述:
第一行包含一个整数n(1\(\leq\) n$\leq$2·10^5).
第二行包含n-1个整数f2,f3,...fn表示节点i的父亲。
第三行包含n个整数,v1,v2,...vn (1 \(\leq\)\(v_i\)$\leq$10^5),分别表示能量球的能量。

心路历程:
根据这个题目的特点,我只能想到应该把能量球放置在深度深的节点上,并且放置的能量球要尽可能的大。但是码力不足,码不出来。提升思维能力啊,多动手模拟样例发现规律

思路:
可以发现当第i个能量球放置在节点x时,它的贡献为:节点i到节点x的深度\(dep_x\)×能量球的能量\(v_i\),于是可以贪心。求出深度dep后,分别对深度dep和能量v排序,之后大的乘大的,再全部加起来即可。
即ans= \(\sum\limits_{i=1}^{n}\)\(dep_i\)·\(v_i\)
经典结论: 两个向量内部允许任意重排,则点积最大方法为都升序排列。

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
int dep[N], v[N];
int main() {

	ios::sync_with_stdio(false);
	cin.tie(0);

	int n, f;
	cin >> n;
	dep[1] = 1;				//根节点的深度为1
	
	//求节点深度
	for (int i = 2; i <= n; i++) {
		cin >> f;
		dep[i] = dep[f] + 1;
	}
	for (int i = 1; i <= n; i++) {
		cin >> v[i];
	}
	
	//重排
	sort(v + 1, v + n + 1);
	sort(dep + 1, dep + n + 1);
	
	LL ans = 0;
	for (int i = n; i; i--) {
		ans += dep[i] * v[i];
	}
	
	cout << ans << endl;
	return 0;
}

总结:代码层面无法优化时,可以考虑先用数学知识优化问题,例如这次的J题。然后开拓思路的H题,非常edu。

posted @ 2023-01-19 14:18  csai_H  阅读(39)  评论(0)    收藏  举报