CSP-J复赛 模拟题3补题报告

CSP-J复赛模拟题3补题报告

2023.10.3 Tue.
S10473吴启瀚

1. 比赛报告

共4题, 第1题0分, 第2题0分, 第3题10分, 第4题0分, 共10分

2. 比赛过程

第一题 数字对应, 有3个暴力想法都没用上, 最后用map套pair套数组, 样例和大数据都过了, 本地看不出内存超限, 用了70min
第二题 技能学习, 数学公式没想出来, 20min后跳过, 检查时只剩15min, 时间不够了
第三题 等于, 想出一个简单的思路事实证明我想的太简单了
第四题 没时间做了, 直接放弃

3. 题解

3.1 数字对应 digit

3.1.1 题目大意:

有一个序列A, 把每个数换成对应的正整数, 构成B序列, 要求:

  • 如果一个3对应4, 那么所有3都要对应4, A中不能有其他数字对应4
  • B中数字不能在A中出现
  • 字典序最小

3.1.2 当时思路:

定义map<int, pair<int[], int> >, pair.first存所有a[i]的下标, pair.second存a[i]的个数
找到不在A中出现的cnt, 把m[a[i]].first中所有的a[]数组改为cnt

3.1.3 题目解析:

将靠前的数字对应成小的数, 如果当前a[i]没有被对应过, 就从小到大寻找未出现过的数字对应
数据范围大时可用map标记, n最大是1e5, 可以用2e5的bool数组存储

3.1.4 AC代码:

#include <iostream>
#include <cstdio>
#include <map>
using namespace std;
const int N = 1e5 + 5;
int n, a[N], cnt = 1;
map<int, int> mp, b;
int main(){
	scanf("%d", &n);
	for(int i = 1;i <= n;i++){
		scanf("%d", &a[i]);
		mp[a[i]]++;
	}
	for(int i = 1;i <= n;i++){
		if(b[a[i]] != 0)
			printf("%d ", b[a[i]]);
		else{
			while(mp[cnt] != 0)
				cnt++;
			b[a[i]] = cnt;
			mp[cnt] = 1;
			printf("%d ", cnt);
		}
	}
	return 0;
}

3.2 技能学习 skill

3.2.1 题目大意:

有n个同学要学习和m份资料, 资料可以随便分配, 只有在学习资料k分以上时才能学习, 每个同学拿到p个学习资料, 会每分钟增p点技能点
技能点最多到Q点, 在学习技能点就不会增长, 共有t分钟, 资料在一开始发下后不能再调整, 求最大技能点和

3.2.2 当时思路:

一点思路也没有

3.2.3 题目解析:

将资料卡着k线发, 余下的平均分给已经发过的人

3.2.4 AC代码:

#include <iostream>
#include <cstdio>
using namespace std;
long long n, m, k, q, t;
long long f1, f2, n1, n2, ans;
int main(){
	scanf("%lld %lld %lld %lld %lld", &n, &m, &k, &q, &t);
	if(n * k > m)
		n = m / k;
	m -= n * k;
	if(m % n != 0){
		f2 = k + m / n + 1;
		n2 = m % n;
	}
	f1 = k + m / n;
	n1 = n - n2;
	ans += min(f1 * t, q) * n1;
	ans += min(f2 * t, q) * n2;
	printf("%lld\n", ans);
	return 0;
}

3.3 等于 equal

3.3.1 题目大意:

给定一个元素属于-2, -1, 1, 2的序列, 求多少个子数组最大值的绝对值等于最小值的绝对值
子数组是连续的, 子序列是不连续的

3.3.2 当时思路:

将a数组值全取绝对值, x记录1出现的个数, y记录2出现的个数, ans += x + y * (y + 1) / 2
遍历找连续1的个数z, ans += z * (z - 1) / 2

3.3.3 题目解析:

有两种情况会出现最大值和最小值的绝对值相等

  1. 区间中只有一种数字, 用for循环统计
  2. 最大和最小值为相反数, 固定左端点, 找合适的右端点
    1. 如果两头是1和-1, 向后找第一个2或-2的上一位, 可以通过维护nxt[i][j]表示a[i]及以后第一个出现j的位置
    2. 是2和-2, 是右端点保证区间里有2和-2

3.3.4 AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 5e5 + 5, inf = 0x3f3f3f3f;
long long n, a[N], ans, nxt[N][5];

int max(int x, int y){
	if(x > y)	return x;
	return y;
}
int min(int x, int y){
	if(x < y)	return x;
	return y;
}

int main(){
	scanf("%d", &n);
	memset(nxt, 0x3f, sizeof(nxt));
	for(int i = 1;i <= n;i++){
		scanf("%d", &a[i]);
	}
	long long cnt, lst;
	for(int i = 2;i <= n;i++){
		if(a[i] == lst)
			cnt++;
		else{
			ans += cnt * (cnt + 1) / 2;
			cnt = 1;
			lst = a[i];
		}
	}
	ans += cnt * (cnt + 1) / 2;
	int pos1, posn1, pos2, posn2, startPos, endPos;
	for(int i = n;i >= 1;i--){
		for(int j = 0;j <= 4;j++)
			nxt[i][j] = nxt[i + 1][j];
		nxt[i][a[i] + 2] = i;
		
		pos1 = nxt[i][3];
		posn1 = nxt[i][1];
		pos2 = nxt[i][4];
		posn2 = nxt[i][0];
		
		startPos = max(pos2, posn2);
		endPos = n + 1;
		if(startPos != inf && startPos < endPos)
			ans += endPos - startPos;
		
		startPos = max(pos1, posn1);
		endPos = min(min(pos2, posn2), (int)n + 1);
		if(startPos != inf && startPos < endPos)
			ans += endPos - startPos;
	}
	printf("%d\n", ans);
	return 0;
}

3.4 最小方差 variance

3.4.1 题目大意:

一棵无根树有n个点, 边权都为1, 找一个树根, 每个结点到根的距离组成序列a, 使的方差最小, 将方差乘 n 2 n^2 n2输出
x为a的平均值, 乘以 n 2 n^2 n2后的方差为 n ∗ ∑ i = 1 n ( a i − x ) 2 n*\sum_{i=1}^{n}(a_i-x)^2 ni=1n(aix)2

3.4.2 当时思路:

时间不够做这道题了

3.4.3 题目解析:

考虑方差的计算式,假设选择点p为根,各点距离存在dis,则此时有 n 2 ∗ D = n ∗ ∑ ( d i s i 2 ) − ( ∑ ( d i s i ) ) 2 n^2 * D = n * \sum(dis_i^2) - (\sum(dis_i))^2 n2D=n(disi2)((disi))2
说明我们需要维护各距离的平方和,以及各距离的和。假设1为整棵树的根,考虑怎么向上计算出结果。
令sum1[u]表示以 为根的子树上各点到u的距离和, sum2[u]表示以u为根的子树上各点到u的距离平方和。假设已有儿子v的答案, 儿子上所有点到父亲的距离会在v原来的基础上加一。因此发现需要维护子树上的点数量, 记为 sz[u]
此时可以写出转移式:
s u m 1 [ u ] = ∑ v ⊂ u − s o n ( s u m 1 [ v ] + s z [ v ] ) sum1[u] = \sum_{v\subset u-son}(sum1[v] + sz[v]) sum1[u]=vuson(sum1[v]+sz[v])
s u m 2 [ u ] = ∑ v ⊂ u − s o n ( s u m 2 [ v ] + 2 ∗ s u m 1 [ v ] + s z [ v ] ) sum2[u] = \sum_{v\subset u-son}(sum2[v] + 2 * sum1[v] + sz[v]) sum2[u]=vuson(sum2[v]+2sum1[v]+sz[v])
这样我们就可以算出以1为根时整棵树的方差, 这也是第一次dfs做的事。
接下来考虑如果换成其它点作为根, 答案会有怎么样的变化。
某一个节点为根时,方差的答案有两个来源: 一个是该结点所在的子树的贡献(这里的子树指的是假设1为根时的子树),还有一个来源是当前点上方的祖先结点对的贡献。(祖先也是指1为根时这个点的祖先)
前者我们已经在第一次dfs中求得。我们来考虑第二个问题如何计算。
首先,将子树的影响从父节点中去掉:
这个点的子树对父亲结点的贡献为:

  1. 对 sz 的贡献: sz[u]
  2. 对 sum1 的贡献: sum1[v] + sz[v]
  3. 对 sum2 的贡献: sum2[v] + 2 * sum1[v] + sz[v]
    不妨将它们记为 szu,ret1,ret2
    从根向下传时, s1 = ret + sz[u], s2 = ret2 + 2 * ret1 + sz[u]
    然后从根出发进行dfs, 向下的过程中记录维护s1, s2的变化, 来获得之后的点作为根时整棵树的方差。

3.4.4 AC代码:

#include <iostream>
#include <cstdio>
#include <vector>
typedef long long ll;
using namespace std;

const int N = 1e5 + 5;
ll n, sum1[N], sum2[N], sz[N], res;
vector<int> G[N];

void dfs1(int u, int f){
	for(int v = 0;v < G[u].size();v++){
		if(v == f)	continue;
		dfs1(v, u);
		sz[u] += sz[v];
		sum1[u] += sum1[v];
		sum2[u] += sum2[v];
	}
	sum2[u] += sz[u] + 2 * sum1[u];
	sum1[u] += sz[u];
	sz[u] += 1;
}
void dfs2(int u, int f, ll s1, ll s2){
	res = min(res, n * (s2 + sum2[u]) - (sum1[u] + s1) * (sum1[u] + s1));
	for(int v = 0;v < G[u].size();v++){
		if(v == f)	continue;
		ll ret1 = sum1[u] - (sum1[v] + sz[v]) + s1;
		ll ret2 = sum2[u] - (sum2[v] + 2 * sum1[v] + sz[v]) + s2;
		ll szu = n - sz[v];
		dfs2(v, u, ret1 + szu, ret2 + 2 * ret1 + szu);
	} 
}

int main(){
	int T;
	scanf("%d", &T);
	while(T--){
		scanf("%d", &n);
		for(int i = 1;i <= n;i++){
			G[i].clear();
			sum1[i] = 0;
			sum2[i] = 0;
			sz[i] = 0;
		}
		for(int i = 1;i < n;i++){
			int u, v;
			scanf("%d %d", &u, &v);
			G[u].push_back(v);
			G[v].push_back(u);
		}
		res = 1e18;
		dfs1(1, 0);
		dfs2(1, 0, 0, 0);
		printf("%d\n", res);
	}
	return 0;
}

4. 反思

4.1 回顾问题

第1题浪费时间太多, 思路不好, 第2题没有思路, 第3题忽略了一些问题

4.2 如何改进

大量联系思考能力, 多做题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值