目录
写在前面
补题地址:https://codeforces.com/gym/104128。
上大分失败呃呃呃呃
有点过载了妈的我现在应该去打会儿游戏。
A 签到
等价于求 \(n\) 在 \(k\) 进制下各位之和,写一个类似快速幂的东西。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
//=============================================================
//=============================================================
int main() {
//freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
LL n, k; std::cin >> n >> k;
LL ans = 0;
if (k == 1) {
ans = n;
} else {
LL x = k;
while (x <= n) x *= k;
x /= k;
while (n) {
if (x <= n) ans += n / x, n %= x;
x /= k;
}
}
std::cout << ans << "\n";
}
return 0;
}
B 数学,模拟
我是打表找的规律哈哈,计算式与下述分析是等价的但是仅使用了乘法避免了开根的精度丢失,也是非常幸运了。
考虑证明。发现每盏灯 \(i\) 被翻转的次数即 \(i\) 的因数个数 \(\operatorname{d}(i)\)。
一个众所周知的结论是对于每个数的因数都是成对儿存在的,即有 \(d | i \iff \frac{i}{d} | i\)。则可知若 \(i\) 为平方数,则 \(\operatorname{d}(i)\) 为奇数,灯将在最后是关闭的,否则 \(\operatorname{d}(i)\) 为偶数,灯在最后是开启的。
于是考虑对于每次询问 \(k\) 二分答案,每次检查 \(\operatorname{mid}\) 中平方数的个数 \(\left\lfloor\sqrt{i}\right\rfloor\),则最小的满足 \(\operatorname{mid} -\left\lfloor\sqrt{i}\right\rfloor = k\) 的 \(\operatorname{mid}\) 即为答案。
呃呃这题数据范围较大仅用 sqrt
开根精度不够,按上述实现需要用 sqrtl
保证返回值为 long double
。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
//=============================================================
//=============================================================
int main() {
//freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
LL k, ans = 1; std::cin >> k;
for (LL l = 1, r = ceil(sqrt(1.0 * k)); l <= r; ) {
LL mid = (l + r) / 2ll;
if (k <= 1ll * mid * (mid + 1)) {
ans = mid;
r = mid - 1;
} else {
l = mid + 1;
}
}
std::cout << k + ans << "\n";
}
return 0;
}
C 二进制,拆位
第一眼看到这个式子感觉非常不可做的样子,万一相减时出现借位怎么办?进一步观察发现是不会出现的。考虑位运算的性质,可知 \(a\) 中为 1 的各位在 \(a \operatorname{or} b\) 中一定存在,\(a \operatorname{and} c\) 中为 1 的各位在 \(a\) 中一定存在,则可以保证 \(a \operatorname{or} b - a \operatorname{and} c\) 一定不会出现借位的情况,于是可以拆位考虑。
然后手玩下发现,对于第 \(i\) 位:
- 若 \(b, c\) 均为 1,则 \(a\) 中该位必须与 \(d\) 中该位相反,才可令该位合法;
- 若 \(b, c\) 均为 0,则 \(a\) 中该位必须与 \(d\) 中该位相同,才可令该位合法;
- 否则无论 \(a\) 取何值都不会影响该位的计算结果,可直接判断该位是否合法;
一个更进一步的做法,是发现上述前两种情况相当于 \(a = b\oplus c\),于是直接检查 \(a=b\oplus c\) 是否合法即可。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
LL a, b, c, d;
//=============================================================
//=============================================================
int main() {
//freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
std::cin >> b >> c >> d;
a = 0;
for (LL i = 0; i < 60; ++ i) {
LL j = (1ll << i);
if ((b >> i & 1) && (c >> i & 1)) {
if (!(d >> i & 1)) a |= j;
} else if (b >> i & 1) {
continue;
} else if (c >> i & 1) {
continue;
} else {
if (d >> i & 1) a |= j;
}
}
if ((a | b) - (a & c) != d) a = -1;
std::cout << a << "\n";
}
return 0;
}
D 暴力,并查集
呃呃赛时写了一坨不知所谓的东西第一次 WA26 然后把关键部分复制了两遍就过了哈哈最后也没 fst。
在题面里就特别指出 \(d_i\le 10\),这个性质一看就非常重要。
考察连通块的数量,且点的数量不太多,考虑使用并查集维护。此时发现对于一次操作所有点两两连边是并无必要的,仅需考察每次操作中每个点与下一个点连边即可。
又 \(d_i\le 10\),则对于每个点仅会向之后连至多十条边,于是考虑直接维护每个点是否向 \(i+j(1\le j\le 10)\) 连边,再并查集维护连通性即可。
可以通过类似差分的方式维护每个点是否被修改。记 \(\operatorname{tag}_{i, j}(1\le j\le 10)\) 表示节点 \(i\) 需要向节点 \(i+j\) 连边的条数,修改时仅需令 \(\operatorname{tag}_{a, d}:=\operatorname{tag}_{a, d} + 1, \operatorname{tag}_{a + kd, d} :=\operatorname{tag}_{a + kd, d} -1\),然后在枚举枚举每个点及连边时更新下一个点的连边数即可。
总时间复杂度 \(O(n)\) 级别。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
//=============================================================
int n, m, fa[kN];
int tag[kN][11];
//=============================================================
int find(int x_) {
return fa[x_] == x_ ? x_ : fa[x_] = find(fa[x_]);
}
void merge(int x_, int y_) {
int fx = find(x_), fy = find(y_);
if (fx == fy) return;
fa[fx] = fy;
}
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
std::cin >> n >> m;
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= 10; ++ j) tag[i][j] = 0;
fa[i] = i;
}
for (int i = 1; i <= m; ++ i) {
int a, d, k; std::cin >> a >> d >> k;
tag[a][d] = tag[a][d] + 1;
tag[a + k * d][d] = tag[a + k * d][d] - 1;
}
int ans = 0;
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= 10; ++ j) {
if (tag[i][j] > 0) {
merge(i, i + j);
tag[i + j][j] = tag[i + j][j] + tag[i][j];
}
}
}
for (int i = 1; i <= n; ++ i) if (find(i) == i) ++ ans;
std::cout << ans << "\n";
}
return 0;
}
/*
1
100 4
1 1 1
1 1 1
1 1 1
2 1 1
100 1 0
100 1
19 2 4
*/
E 概率DP
妈的这题 std 明明是 \(O(n\log^2 v)\) 的写那么麻烦但为什么会把 \(O(nv)\) 的暴力放过去?出题人犯病了?
首先有一个非常简单的暴力 DP 并且可以过这题呃呃,考虑记 \(f_{i, j}\) 表示随机选择 \(a_1\sim a_i\),异或和为 \(j\) 的概率,初始化 \(f_{0, 0} = 1\),则有显然的类似背包的转移:
\[\begin{cases} f_{i, j}\leftarrow \frac{p_1}{10^4}\times f_{i - 1, j}\\ f_{i, j \oplus a_i} \leftarrow \left(1-\frac{p_1}{10^4}\right)\times f_{i - 1, j} \end{cases}\]答案即为:
\[\sum_{i=0}^{1023} f_{n, i}\times i^2 \]时间复杂度 \(O(nv)\) 级别,可过但是太傻比了显然是出题人出题唐氏了这都没想到就嗯造数据给放过去了。
\(O(nv)\) 暴力 DP:
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
const LL p = 1e9 + 7;
//=============================================================
int n, a[kN], prob[kN];
LL f[2][1024];
//=============================================================
LL qpow(LL x_, LL y_) {
LL ret = 1;
while (y_) {
if (y_ & 1) ret = ret * x_ % p;
x_ = x_ * x_ % p, y_ >>= 1ll;
}
return ret;
}
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
LL inv = qpow(10000, p - 2);
int T; std::cin >> T;
while (T --) {
std::cin >> n;
for (int i = 1; i <= n; ++ i) std::cin >> a[i];
for (int i = 1; i <= n; ++ i) std::cin >> prob[i];
int now = 1;
for (int i = 0; i <= 1023; ++ i) f[0][i] = 0;
f[0][0] = 1;
for (int i = 1; i <= n; ++ i) {
LL p1 = prob[i] * inv % p, p2 = (1 - p1 + p) % p;
for (int j = 0; j <= 1023; ++ j) f[now][j] = p2 * f[now ^ 1][j] % p;
for (int j = 0; j <= 1023; ++ j) f[now][j ^ a[i]] += p1 * f[now ^ 1][j] % p;
for (int j = 0; j <= 1023; ++ j) f[now][j] %= p;
now ^= 1;
}
LL ans = 0;
for (int i = 0; i <= 1023; ++ i) ans += f[now ^ 1][i] * i * i % p, ans %= p;
std::cout << ans << "\n";
}
return 0;
}
/*
1
1
1
10000
*/
F
写在最后
学到了什么:
- B:打表好用。
- C:经典拆位考虑。