D-Chocolate
题意 n × m
的巧克力,甲乙每人选定 [x, y]
,吃掉 [0, 0]
到 [x, y]
之间矩形区域的巧克力,每次至少吃一个单位,吃完最后一块巧克力的人输,甲先吃
Tag 博弈论
题解 不妨设 n < m
,有以下结论
n = 1, m = 1
时,乙win
n = 1, m > 1
时,当甲选择x = 1, y = 1
时候,乙选择x = 1, y = m - 1
,甲只能选择x = 1, y = m
,乙win
,但是甲可以选择执行乙的第一步操作,从而甲一定win
n > 1, m > 1
时,同上个情况,甲永远可以领先一步下乙的操作,因此甲一定win
#include <iostream>
using namespace std;
int n, m;
const string a = "Kelin";
const string b = "Walk Alone";
string solve() {
if (n == 1 && m == 1) return b;
return a;
}
int main() {
cout << solve() << endl;
return 0;
}
H-Matches
题意 给定序列 a
和 b
,交换一个其中一个序列的两个数使得
∑
n
i
=
1
∣
a
i
−
b
i
∣
\sum_{n}^{i = 1}\left | a_{i} - b_{i} \right |
∑ni=1∣ai−bi∣ 最小
题解 def:若 a[i] < b[i]
则 (a[i], b[i])
称之为正序列,a[i] > b[i]
称之为反序列
target: 求正序列和逆序列的最大重叠区间
输入 x
和 y
,按照 y
倒序排序,保证接下来枚举的时候 y
依次递减
对于逆序序列,设最大重叠区间为 [l, r]
,即 v[i].d < 0
,维护 m1 = min(m1, v[i].x)
。l = max(v[i].x, m2)
,r = v[i].y
(因为已保证 y
依次递减)。
对于正序序列,操作同上。进行 swap()
操作后,res -= len * 2
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 1e6 + 2;
int n;
struct Node {
int x, y, d;
bool operator < (const Node & W) const {
return y > W.y;
}
} v[N];
int main() {
cin >> n;
for (int i = 0; i < n; i ++) cin >> v[i].x;
for (int i = 0; i < n; i ++) cin >> v[i].y;
ll res = 0;
for (int i = 0; i < n; i ++) {
v[i].d = v[i].y - v[i].x;
if (v[i].d < 0) swap(v[i].x, v[i].y);
res += abs(v[i].d);
}
sort(v, v + n);
int m1 = 2e9, m2 = 2e9, len = 0;
for (int i = 0; i < n; i ++) {
if (v[i].d < 0) {
len = max(len, v[i].y - max(v[i].x, m2));
m1 = min(m1, v[i].x);
} else if (v[i].d > 0) {
len = max(len, v[i].y - max(v[i].x, m1));
m2 = min(m2, v[i].x);
}
}
cout << res - (ll)len * 2 << endl;
return 0;
}
J-Roulette
题意 n
喜欢一个游戏,第一次投入 x[i] = 1
,之后有一半几率赢 x[i]
,有一半几率失去 x[i]
,赢一局后下一次投入 x[i] = 1
,输了投入 x[i] = 2 * x[i - 1]
,初始有 n
元,问有多少几率能赢至 n + m
元
Tag 快速幂
逆元
题解 快速幂求逆元,可由费马小定理得出 inline int qpow(ll a, int b = mod - 2, int m = mod)
当
n
为质数时,可以用快速幂求逆元:
a / b ≡ a * x (mod n)
两边同乘b可得a ≡ a * b * x (mod n)
即1 ≡ b * x (mod n)
同b * x ≡ 1 (mod n)
由费马小定理可知,当n为质数时
b ^ (n - 1) ≡ 1 (mod n)
拆一个b出来可得b * b ^ (n - 2) ≡ 1 (mod n)
故当n为质数时,b的乘法逆元x = b ^ (n - 2)
不难注意到,以”输输输……输赢”为一个周期,可以赢到一块钱,因而需要经过 m
个这样的周期。每个周期不会输光的条件:如果当前有 x
元,那么不能连输超过 r
次,r
是最大满足
2
r
−
1
≤
x
2^{r} - 1 \le x
2r−1≤x 的正整数,成功的概率为
1
−
(
1
2
)
r
1 - (\frac{1}{2})^{r}
1−(21)r。再从 n
枚举到 n + m
,此时
n
o
w
=
log
2
(
x
+
1
)
now = \log_{2}{(x + 1)}
now=log2(x+1),每次区间是
[
2
r
−
1
−
1
,
2
r
+
1
−
2
]
[2^{r - 1} - 1, 2^{r + 1} - 2]
[2r−1−1,2r+1−2],每个区间的长度为 r - n
,几率最后累乘即可
对于其中的快速幂求逆元运算,其中 ( 1 2 ) r = 2 − r = 2 − r + 1 − 1 = 2 − r + 1 × 2 m o d − 2 = 2 m o d − r − 1 (\frac{1}{2})^{r} = 2^{-r} = 2^{-r + 1 - 1} = 2^{-r + 1} \times 2^{mod - 2} = 2^{mod - r - 1} (21)r=2−r=2−r+1−1=2−r+1×2mod−2=2mod−r−1
#include <iostream>
#include <cmath>
using namespace std;
typedef long long ll;
const int mod = 998244353;
int n, m;
inline int qpow(ll a, int b = mod - 2, int m = mod) {
ll res = 1;
while (b) {
if (b & 1) (res *= a) %= m;
(a *= a) %= m;
b >>= 1;
}
return res;
}
int main() {
cin >> n >> m;
m += n;
ll res = 1;
while (n < m) { // m 是指最后一个区间的右端点的下一个
int now = log2(n + 1);
int r = min(m, (1 << (now + 1)) - 1);
int powValue = 1 - qpow(2, mod - now - 1) + mod; // 加上 mod 是为了防止负数
(res *= qpow(powValue, r - n)) %= mod;
n = r;
}
cout << res << endl;
return 0;
}
K-Subdivision
题意 n
个节点 m
条边的无向图,可以进行无限次操作,每次选择一条边,断开,并添加一个点,分别与原来的两个点相连,求能存在满足 dis <= k
的最多有多少个点
题解 用 vector
记录每个节点的邻接表,从根节点 1
依次遍历,每次 res ++
,如果是叶节点 res += k - d[t]
,如果没有被遍历过,则标记 vis[i] = 1
,记录父节点为 p[i] = t
,使得下次不能逆向遍历,同时更新距离 d[i] = d[t] + 1
,若 d[i] <= k
则推入 queue
中;若被遍历过,则 res += k - d[t]
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int N = 1e5 + 2;
int n, m, k;
int main() {
cin >> n >> m >> k;
vector<vector<int>> v(n + 1);
vector<int> d(n + 1, -1);
vector<int> p(n + 1);
vector<int> st(n + 1);
while (m --) {
int a, b; cin >> a >> b;
v[a].push_back(b), v[b].push_back(a);
}
queue<int> q;
q.push(1);
d[1] = 0;
st[1] = 1;
ll res = 0;
while (!q.empty()) {
int t = q.front();
q.pop();
res ++;
if (v[t].size() == 1 && t != 1) res += k - d[t];
for (auto i : v[t]) {
if (p[t] == i) continue;
if (st[i] == 0) {
st[i] = 1;
p[i] = t;
d[i] = d[t] + 1;
if (d[i] <= k) q.push(i);
} else res += k - d[t];
}
}
cout << res << endl;
return 0;
}
另一种用度数求解的方法,妙啊 😉
先从1
开始 bfs
,同时求出每个点到 1
的最短路。
考虑选什么边来加点,两种情况:
第一种,删除该边不影响任何一个点到 1
的最短距离。 如何寻找这样的边,若一个点到 1
的最短距离小于等于 k
且该点有 2
个以上的出度/入度,则存在出度/入度减 2
的这样的边。
第二种,删除该边影响某点到 1
的最短路,那么这样的整一段最多为答案提供 k
的贡献。
#include "bits/stdc++.h"
using namespace std;
using i64 = long long;
int main() {
int n, m, k;
cin >> n >> m >> k;
vector<vector<int>> g(n);
vector<int> deg(n);
for (int i = 0; i < m; i++) {
int u, v; cin >> u >> v;
u--, v--;
g[u].push_back(v), g[v].push_back(u);
deg[u]++, deg[v]++;
}
queue<int> q;
vector<int> dis(n, -1);
q.push(0);
dis[0] = 0;
while (!q.empty()) {
auto u = q.front();
q.pop();
for (auto &v : g[u]) {
if (dis[v] == -1) {
dis[v] = dis[u] + 1;
q.push(v);
}
}
}
i64 ans = 1 + k * deg[0];
for (int i = 1; i < n; i++)
if (deg[i] >= 2 && dis[i] != -1 && dis[i] <= k)
ans += 1LL * (k - dis[i]) * (deg[i] - 2);
cout << ans << endl;
return 0;
}