前段时间太忙了 Round B,C 都没打。
Image Labeler
给定一个大小为 N N N 的序列 { A i } \{A_i\} {Ai} 将其分在 M M M 个集合中,每个集合中至少有一个数,问每个集合中位数之和的最大是多少?
贪心,如果把最大的数单独放在一个集合中,显然该集合的中位数为这个数。所以将序列排序后,最大的 M − 1 M-1 M−1 个数放在前 M − 1 M-1 M−1 个集合中,剩下的数放在 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 K≤3000
进一步剖析题目,就会发现,删去的数一定为 A A A 序列的头一段尾一段加上 B B B 序列的头一段尾一段。由于 K K K 不太大,预处理出前缀和后,我们可以通过枚举,得出从 A A A 中删去 i ( i ≤ K ) i(i\le K) i(i≤K) 个数的最大值 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}| maxai∑i=2n∣ai−ai−1∣
动态规划。记 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 K≤20,N,M≤105
证实的方法为反证法,先假设 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;
}