Kick Start Round D 2022

前段时间太忙了 Round B,C 都没打。

Image Labeler

给定一个大小为 N N N 的序列 { A i } \{A_i\} {Ai} 将其分在 M M M 个集合中,每个集合中至少有一个数,问每个集合中位数之和的最大是多少?

贪心,如果把最大的数单独放在一个集合中,显然该集合的中位数为这个数。所以将序列排序后,最大的 M − 1 M-1 M1 个数放在前 M − 1 M-1 M1 个集合中,剩下的数放在 1 1 1 个集合中即可。

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int main(){
	int T;
	cin >> T;
	int A[100010];
	for(int i = 1; i <= T; ++i){
		int N, M; cin >> N >> M;
		for(int j = 1; j <= N; ++j) cin >> A[j];
		sort(A + 1, A + 1 + N);
		double ans = 0;
		for(int j = 1; j < M; ++j)
			ans += A[N - j + 1];
		int cnt = N - M + 1;
		if(cnt % 2 == 0) ans += (A[cnt / 2] + A[cnt / 2 + 1]) / 2.;
		else ans += A[cnt / 2 + 1];
		printf("Case #%d: %lf\n", i, ans);
	}
	return 0;
}

Maximum Gain

给定两个序列 A A A B B B,进行 K K K 次操作,每次从 A A A B B B 序列的开头或者末尾选择一个数删除,问删去的数之和的最大值是多少?
K ≤ 3000 K\le 3000 K3000

进一步剖析题目,就会发现,删去的数一定为 A A A 序列的头一段尾一段加上 B B B 序列的头一段尾一段。由于 K K K 不太大,预处理出前缀和后,我们可以通过枚举,得出从 A A A 中删去 i ( i ≤ K ) i(i\le K) i(iK) 个数的最大值 f A i {f_A}_i fAi B B B f B i {f_B}_i fBi。 然后枚举两者之和找出最大值即可。注意有可能没有删去头/尾段或者没有删去 A A A B B B 序列。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
using namespace std;
void calc(long long *S, int *A, int K, int N){
	long long W[6010];
	for(int i = 1; i <= N; ++i) W[i] = W[i - 1] + A[i];
	for(int i = 1; i <= min(K, N); ++i)
		for(int j = 0; j <= i; ++j)
			S[i] = max(S[i], W[j] + W[N] - W[N - (i - j)]);
}
int main(){
	int T;
	cin >> T;
	int A[6010], B[6010];
	long long AS[6010], BS[6010];
	for(int i = 1; i <= T; ++i){
		int N, M;
		cin >> N;
		for(int j = 1; j <= N; ++j) cin >> A[j];
		cin >> M;
		for(int j = 1; j <= M; ++j) cin >> B[j];
		int K; cin >> K;
		memset(AS, 0, sizeof(AS));
		calc(AS, A, K, N);
		memset(BS, 0, sizeof(BS));
		calc(BS, B, K, M);
		long long ans = 0;
		for(int j = 0; j <= K; ++j) {
			if(j > N) continue;
			if(K - j > M) continue;
			ans = max(ans, AS[j] + BS[K - j]);
		}
		printf("Case #%d: %lld\n", i, ans);
	}
	return 0;
}

Touchbar Typing

给定一个单词串 S S S 和一个文本串 K K K,依次从 K K K 中选出值为 S i S_i Si 的字符 K a i K_{a_i} Kai,求 max ⁡ a i ∑ i = 2 n ∣ a i − a i − 1 ∣ \max_{a_i} \sum_{i=2}^{n} |a_i-a_{i-1}| maxaii=2naiai1

动态规划。记 f [ i ] [ j ] f[i][j] f[i][j] 表示从 K K K 中选出值为 S i S_i Si 的字符 K j K_j Kj 的最大值。由于 K K K 中可能有多个重复值的数,此时根据贪心肯定选择距离当前位置最近的数。于是预处理一下当前在位置 i i i 时,下一个和上一个值为 j j j 的数在哪个位置。于是 O ( n ) O(n) O(n) 递推一下即可。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
using namespace std;
int nx[2510][2510], pv[2510][2510], f[2510][2510];
int main(){
	int T;
	cin >> T;
	int S[2510], K[2510];
	for(int i = 1; i <= T; ++i){
		memset(nx, -1, sizeof(nx));
		memset(pv, -1, sizeof(pv));
		int N, M;
		cin >> N;
		for(int j = 1; j <= N; ++j) cin >> S[j];
		cin >> M;
		for(int j = 1; j <= M; ++j) cin >> K[j];
		for(int j = 1; j <= M; ++j){
			for(int k = 1; k <= 2500; ++k)
				pv[j][k] = pv[j - 1][k];
			pv[j][K[j]] = j;
		}
		for(int j = M; j; --j){
			for(int k = 1; k <= 2500; ++k)
				nx[j][k] = nx[j + 1][k];
			nx[j][K[j]] = j;
		}
		memset(f, 66, sizeof(f));
		for(int j = 1; j <= M; ++j) if(K[j] == S[1]) f[1][j] = 0;
		for(int j = 1; j < N; ++j){
			for(int k = 1; k <= M; ++k){
				if(f[j][k] >= f[0][0]) continue;
				if(nx[k][S[j+1]] != -1){
					int &tmp = f[j+1][nx[k][S[j+1]]];
					tmp = min(tmp, f[j][k] + abs(k - nx[k][S[j+1]]));
				}
				if(pv[k][S[j+1]] != -1){
					int &tmp = f[j+1][pv[k][S[j+1]]];
					tmp = min(tmp, f[j][k] + abs(k - pv[k][S[j+1]]));
				}

			}
		}
		int ans = 1e9;
		for(int j = 1; j <= M; ++j) ans = min(ans, f[N][j]);
		printf("Case #%d: %d\n", i, ans);
	}
	return 0;
}

Suspects and Witnesses

现场有 N N N 个人,已知最多 K K K 个人偷走蛋糕,且有 M M M 条证词,分别为 A i A_i Ai 认为 B i B_i Bi 没有偷走蛋糕,如果一个人没有偷走蛋糕,则他说的话一定为真。问有多少人可以被证实一定没有偷走蛋糕?
K ≤ 20 , N , M ≤ 1 0 5 K\le 20,N,M\le 10^5 K20,N,M105

证实的方法为反证法,先假设 i i i 偷走了蛋糕,然后指认他没偷走蛋糕的人一定说假话了,那一定也偷走了蛋糕,依次来反推出有多少个人偷走了蛋糕,如果超过 K K K 个则与题意矛盾,则 i i i 一定没偷走蛋糕。

于是根据证词连边,然后在边上进行 DFS,如果遍历了超过 K K K 个点,则一定没偷走蛋糕,复杂度 O ( M K ) O(MK) O(MK)

#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
vector<int> e[100010];
int Q[25], cnt;
bool vis[100010];
bool dfs(int u, int lim){
	Q[++cnt] = u;
	vis[u] = 1;
	if(cnt > lim) return 1;
	for(auto v: e[u]){
		if(vis[v] == 0){
			if(dfs(v, lim) == 1) return 1;
		}
	}
	return 0;
}
int main(){
	int T;
	cin >> T;
	for(int i = 1; i <= T; ++i){
		int N, M, K; cin >> N >> M >> K;
		for(int j = 1; j <= N; ++j) e[j].clear();
		for(int j = 1; j <= M; ++j){
			int A, B; cin >> A >> B;
			e[B].push_back(A);
		}
		int ans = 0;
		for(int j = 1; j <= N; ++j){
			int tmp = dfs(j, K);
			ans += tmp;
			for(int k = 1; k <= cnt; ++k) vis[Q[k]] = 0;
			cnt = 0;
		}
		printf("Case #%d: %d\n", i, ans);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值