CCPC 2021 山东省赛
G. Grade Point Average
题目大意
求n个数字的平均数,保留k位输出
解题思路
C++按除法过程模拟或者Python decimal,注意要格式化转为非科学计数法输出(某些神秘原因会导致Python3 ac但是pypy3 re)
代码实现
#include <bits/stdc++.h>
using i64 = long long;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
i64 n, k, sum = 0, f = 1;
std::cin >> n >> k;
for (int i = 0; i < n; i++) {
int x;
std::cin >> x;
sum += x;
}
while (1) {
if (k == 0) {
break;
}
k -= !f;
std::cout << sum / n;
sum -= sum / n * n;
if (sum / n == 0 && f) {
std::cout << ".";
f = 0;
}
if (sum == 0) {
break;
}
if (sum < n) {
sum *= 10;
}
}
std::cout << std::string(k, '0');
}
from decimal import *
getcontext().prec = int(1e5 + 10)
n, k = map(Decimal, input().split())
a = sum(map(int, input().split()))
ans = (Decimal(a) / n).quantize(Decimal("0." + ("0" * int(k - 1)) + "1"), rounding=ROUND_DOWN)
print(f"{ans:.{int(k)}f}")
H. Adventurer's Guild
题目大意
初始有H的生命和S的耐力,有n个任务,第i个任务会消耗\(h_i\)的生命和\(s_i\)的耐力,得到\(w_i\)个金币,当耐力不足的时候会扣除等价的生命,不死的情况下最多能得到多少金币
解题思路
二维背包即可,优先考虑消耗耐力
代码实现
#include <bits/stdc++.h>
using i64 = long long;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, h, s;
std::cin >> n >> h >> s;
std::vector<i64> H(n + 1), S(n + 1), W(n + 1);
std::vector<std::vector<i64>> dp(h + 1, std::vector<i64>(s + 1));
for (int i = 1; i <= n; i++) {
std::cin >> H[i] >> S[i] >> W[i];
}
for (int i = 1; i <= n; i++) {
for (int j = s; j >= 0; j--) {
for (int k = h; k >= 1; k--) {
if (j >= S[i] && k > H[i]) {
dp[k][j] = std::max(dp[k][j], dp[k - H[i]][j - S[i]] + W[i]);
} else if (j < S[i] && j + k - S[i] > H[i]) {
dp[k][j] = std::max(dp[k][j], dp[j + k - S[i] - H[i]][0] + W[i]);
}
}
}
}
std::cout << dp[h][s] << "\n";
}
D. Dyson Box
题目大意
给你一个n*n的矩阵,会填充一些方格,问这些方格朝x轴和y轴掉落的周长分别是多少
解题思路
看当前添加的这个放个的xy以及周围的xy有没有其他方格就能知道每个方格的贡献
代码实现
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
const int N = 2e5 + 10;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, ansa = 0, ansb = 0;
std::cin >> n;
std::vector<int> a(N), b(N);
for (int i = 0; i < n; i++) {
int x, y, cnta = 4, cntb = 4;
std::cin >> x >> y;
if (a[x]) {
cnta -= 2;
}
if (b[y]) {
cntb -= 2;
}
a[x]++;
b[y]++;
cnta -= ((int)(a[x] <= a[x - 1]) + (int)(a[x] <= a[x + 1])) * 2;
cntb -= ((int)(b[y] <= b[y - 1]) + (int)(b[y] <= b[y + 1])) * 2;
ansa += cnta;
ansb += cntb;
std::cout << ansa << " " << ansb << "\n";
}
}
M. Matrix Problem
题目大意
给出一个01矩阵c,要求构造两个01矩阵ab,使得\(a_{ij} \& b_{ij} = c_{ij}\),同时在这个条件下要满足ab矩阵所有的1都联通
解题思路
a第一列填1,然后隔行全填1(最后列不填),b与a的01状态完全相反,此时所有的与都是0,并且在仍和位置填1都会是联通的,最后根据\(c_{ij}\)来改变ab的值即可
代码实现
#include <bits/stdc++.h>
using i64 = long long;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, m;
std::cin >> n >> m;
std::vector<std::vector<int>> a(n, std::vector<int>(m)), b(n, std::vector<int>(m)), c(n, std::vector<int>(m));
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
char ch;
std::cin >> ch;
c[i][j] = ch - '0';
}
}
for (int i = 0; i < n; i += 2) {
for (int j = 0; j < m - 1; j++) {
a[i][j] = 1;
}
if (i + 1 < n) {
a[i + 1][0] = 1;
}
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
b[i][j] = 1 - a[i][j];
}
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (c[i][j]) {
a[i][j] = 1;
b[i][j] = 1;
}
}
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
std::cout << a[i][j];
}
std::cout << "\n";
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
std::cout << b[i][j];
}
std::cout << "\n";
}
}
C. Cat Virus
题目大意
给树黑白染色,要求黑节点的子节点都是黑色的,请你构造出一棵树使得染色方案数恰好是k,保证有解
解题思路
设f(u)为到节点u的染色方案数
- 当u是白色时,u的子节点v染色方案数为f(v),它们各自独立,会贡献\(\prod_{i=1}^{n} f(v_i)\)
- 当u是黑色时候,子节点v们只能贡献1
于是得到转移方程:
\(f(u) = 1 + \prod_{i=1}^{n} (f(v_i) + 1)\)
然后dfs每次k先减去1,表示根节点已染色,在剩下的方案数中分解k即可,以构造二叉树为例,把子树方案分解为a和b使得a*b=k,对k的奇偶性分别讨论即可,log次就能算完
代码实现
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
signed main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
i64 k, v = 1;
std::cin >> k;
std::vector<std::array<i64, 2>> g;
auto dfs = [&](auto &&self, i64 k, int u) -> void {
k--;
i64 a, b;
if (k % 2 == 1) {
a = 1;
b = k;
} else {
a = 2;
b = k / 2;
}
if (a >= 2) {
g.push_back({u, v});
self(self, a, v++);
}
if (b >= 2) {
g.push_back({u, v});
self(self, b, v++);
}
};
dfs(dfs, k, v++);
std::cout << v - 1 << "\n";
for (auto [u, v] : g) {
std::cout << u << " " << v << "\n";
}
}
B. Build Roads
题目大意
n个数字求前n-1小的两两gcd之和
解题思路
如果n较小,以gcd为边权求mst即可。如果n较大R-L较小,则n个数会把这个区间填满,相邻数字必定互质,答案是n-1;如果n较大R-L较大,则极大概率(2e5范围素数最远距离不到100)会有素数,答案还是n-1,特判LR相等的情况
还有一种启发式做法,对于每个数字都随机选点取最小的gcd作为贡献,由于上述性质,随机出错的概率较小,只要设置合适的随机次数最后也能得到答案,注意最后要减去一个最小的gcd,因为它会在产生这个gcd的两个数都计算一次
代码实现
// 做法1
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
struct DSU {
std::vector<int> fa, rank, siz;
int cnt;
DSU(int n) : fa(n + 1), rank(n + 1), siz(n + 1, 1), cnt(n) {
for (int i = 1; i <= n; i++) {
fa[i] = i;
}
}
int find(int x) {
if (fa[x] != x) {
fa[x] = find(fa[x]);
}
return fa[x];
}
void merge(int x, int y) {
int X = find(x), Y = find(y);
if ((X != Y)) {
siz[X] += siz[Y];
if (rank[X] >= rank[Y]) {
fa[Y] = X;
rank[X] += (int)(rank[X] == rank[Y]);
} else {
fa[X] = Y;
}
cnt--;
}
}
int size() {
return cnt;
}
int count(int x) {
return siz[find(x)];
}
};
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
u64 n, L, R, seed, ans = 0;
std::cin >> n >> L >> R >> seed;
auto xorshift64 = [&]() -> u64 {
u64 x = seed;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
return seed = x;
};
auto gen = [&]() -> int { return xorshift64() % (R - L + 1) + L; };
std::vector<i64> a(n);
for (int i = 0; i < n; i++) {
a[i] = gen();
}
if (L == R) {
std::cout << a[0] * (n - 1) << "\n";
} else if (n > 1000) {
std::cout << n - 1 << "\n";
} else {
DSU dsu(n);
std::vector<std::array<i64, 3>> g;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
g.push_back({std::__gcd(a[i], a[j]), i, j});
}
}
std::sort(g.begin(), g.end());
for (auto [w, u, v] : g) {
if (dsu.find(u) != dsu.find(v)) {
ans += w;
dsu.merge(u, v);
}
}
std::cout << ans << "\n";
}
}
// 做法2
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
u64 n, L, R, seed;
std::cin >> n >> L >> R >> seed;
auto xorshift64 = [&]() -> u64 {
u64 x = seed;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
return seed = x;
};
auto gen = [&]() -> int { return xorshift64() % (R - L + 1) + L; };
std::vector<i64> a(n);
for (int i = 0; i < n; i++) {
a[i] = gen();
}
i64 ans = 0, min = LONG_LONG_MAX;
for (int i = 0; i < n; i++) {
i64 g = a[i];
for (int j = 0; j < 300 && g != 1; j++) {
g = std::min(g, std::__gcd(a[i], a[rand() % n]));
}
min = std::min(min, g);
ans += g;
}
std::cout << ans - min << "\n";
}
F. Birthday Cake
题目大意
给你n个字符串,问有多少对字符满足加起来之后左右两部分相等
解题思路
字符串哈希,统计整个串出现的次数和去掉首尾相同部分出现的次数即可
代码实现
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
const int MOD1 = 1e9 + 7, MOD2 = 1e9 + 9;
class StringHash {
public:
int P1, P2;
std::vector<u64> h1, h2, p1, p2;
StringHash() {
std::mt19937 rng(std::chrono::steady_clock::now().time_since_epoch().count());
// P1 = std::uniform_int_distribution<int>(128, 10000)(rng);
// P2 = std::uniform_int_distribution<int>(128, 10000)(rng);
P1 = 131;
P2 = 13331;
}
template <typename Sequence>
void build(const Sequence& seq) {
int n = seq.size();
h1.resize(n + 1, 0);
h2.resize(n + 1, 0);
p1.resize(n + 1, 1);
p2.resize(n + 1, 1);
for (int i = 1; i <= n; i++) {
h1[i] = (h1[i - 1] * P1 + seq[i - 1]) % MOD1;
h2[i] = (h2[i - 1] * P2 + seq[i - 1]) % MOD2;
p1[i] = (p1[i - 1] * P1) % MOD1;
p2[i] = (p2[i - 1] * P2) % MOD2;
}
}
std::pair<u64, u64> get(int l, int r) {
u64 hash1 = (h1[r] - h1[l - 1] * p1[r - l + 1] % MOD1 + MOD1) % MOD1;
u64 hash2 = (h2[r] - h2[l - 1] * p2[r - l + 1] % MOD2 + MOD2) % MOD2;
return {hash1, hash2};
}
};
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
StringHash sh;
std::map<std::pair<u64, u64>, int> cnt1, cnt2;
i64 n, ans = 0;
std::cin >> n;
for (int i = 0; i < n; i++) {
std::string s;
std::cin >> s;
sh.build(s);
int siz = s.size();
auto tmp = sh.get(1, siz);
ans += cnt1[tmp]++;
ans += cnt2[tmp];
for (int len = 1; len * 2 <= siz; len++) {
auto pre = sh.get(1, len);
auto suff = sh.get(siz - len + 1, siz);
if (pre == suff) {
auto mid = sh.get(len + 1, siz - len);
ans += cnt1[mid];
cnt2[mid]++;
}
}
}
std::cout << ans << "\n";
}

浙公网安备 33010602011771号