比赛链接:
https://codeforces.com/gym/103428
A. Goodbye, Ziyin!
题目大意:
\(n - 1\) 条边连接 \(n\) 个点,但是不知道根节点是哪个,判断有多少个节点作为根节点时,树是二叉树。
思路:
当某个节点的度等于 3 的时候,它肯定不能作为根节点。
当某个节点的度大于 3 的时候,不论如何都不可能形成二叉树,即没有一个节点能作为根节点。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int a, b, e[N], ans, n;
int main(){
cin >> n;
for (int i = 1; i <= n - 1; ++ i){
scanf("%d%d", &a, &b);
e[a]++;
e[b]++;
}
for (int i = 1; i <= n; ++ i){
if (e[i] > 3){
cout << "0\n";
return 0;
}
if (e[i] <= 2) ans++;
}
cout << ans << "\n";
return 0;
}
D. Period
题目大意:
给一个字符串 \(s\),\(q\) 组询问,将第 \(q_i\) 位字符改成 '#' 之后,找出这个字符串有几个周期。
周期的解释:
设周期为 \(T\)(\(1 <= T < \lvert s \rvert\)),对于 $ i \in (T, \lvert s \rvert],s[i] = s[i - T]$。
思路:
因为一个字符串中只有一个 '#',所以整个字符串中只有两部分相同,且一个是前缀,一个是后缀。
于是想到 \(kmp\) 去预处理一下 \(next\) 数组,这样子整个字符串的最大相同的前缀和后缀长度已知了。
接着通过循环去统计每一个位置上可能出现的前缀和后缀相同的数量,这样可以得到一个差分数组,然后做一个前缀和。
查询的过程就是找当前这个位置改为 '#' 后,前面和后面的最小长度,输出这个长度所对应的前缀和后缀相同的数量就行,时间复杂度就为 O(1)。
举个栗子:
颜色相同的两段字符串都是相同的,如果将 abcdabc 后面一位字符改为 '#',那么周期数为 2(假设大于这个长度的没有相同的周期了),因为两种颜色所对应的都可以形成周期。
假设对于整段的字符串,最大相同的前缀和后缀就是 \(abcdabc\),同时,对于 \(abcdabc\) 这一段最大相同的前缀和后缀就是 \(abc\),这样子通过 \(next\) 数组可以不断地向前定位,找到每一个前缀和后缀相同的位置并统计数量,这就是前缀和数组记录的过程。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int len, q, p, ne[N], ans[N];
char s[N];
void init(){
len = strlen(s + 1);
for (int i = 2, j = 0; i <= len; ++ i){
while (j && s[i] != s[j + 1]) j = ne[j];
if (s[i] == s[j + 1]) j++;
ne[i] = j;
}
int t = len;
while (t != 0){
ans[t]++;
t = ne[t];
}
for (int i = 1; i <= len; ++ i)
ans[i] += ans[i - 1];
}
int main(){
cin >> s + 1 >> q;
init();
while (q --){
scanf("%d", &p);
int t = min(p - 1, len - p);
cout << ans[t] << "\n";
}
return 0;
}
G. Desserts
题目大意:
\(n\) 种蛋糕,每种蛋糕有 \(a_i\) (\(1 <= i <= n\)) 个,分给 \(k\) 个队伍,\(k \in [1, m]\),蛋糕要分完,每种队伍只能拿同一种蛋糕的一个。问有几种分法,对于每一个 \(k\) 都要输出答案,答案对 998244353 取模。
思路:
\(j \in [1, n]\)、\(i \in [1, m]\),答案就是 \(C_i^{a[j]}\) 的和,暴力做的时间复杂度为 O(n * m),超时,需要优化。
因为数据是小于 1e5 的,所以可以统计同数量的蛋糕有几个,通过快速幂去做它们的 \(C_i^{a[j]}\)。
同时,\(C_i^{a[j]}\) 也可以通过预处理求出来,因为要取模,所以通过逆元来做。
代码:
#include <bits/stdc++.h>
using namespace std;
const int mod = 998244353, N = 5e4 + 10, M = 1e5 + 7;
#define LL long long
LL n, m, a[N], mx, s[M], sv[M], cnt[M];
set <LL> num;
LL qp(LL a, LL b){
if (b == 0) return 1;
LL ans = 1;
while (b != 0){
if (b & 1) ans = (ans * a) % mod;
b >>= 1;
a = (a * a) % mod;
}
return ans % mod;
}
LL inv(LL x){
return qp(x, mod - 2);
}
LL C(LL n, LL m){
if (m > n) return n;
return s[n] * sv[m] % mod * sv[n - m] % mod;
}
void init(){
s[0] = 1;
for (LL i = 1; i <= M - 7; ++ i)
s[i] = s[i - 1] * i % mod;
sv[M - 7] = inv(s[M - 7]);
for (LL i = M - 8; i >= 0; -- i)
sv[i] = sv[i + 1] * (i + 1) % mod;
}
int main(){
init();
cin >> n >> m;
for (int i = 1; i <= n; ++ i){
scanf("%lld", &a[i]);
mx = max(mx, a[i]);
num.insert(a[i]);
cnt[a[i]]++;
}
for (int i = 1; i <= m; ++ i){
if (i < mx) cout << "0\n";
else{
LL ans = 1;
for (auto x : num)
ans = ( ans * qp(C(i, x), cnt[x]) ) % mod;
cout << ans << "\n";
}
}
return 0;
}
J. Circular Billiard Table
题目大意:
圆形桌面,球从边缘上某一个点以 \(\frac{a}{b}\) 这个角度入射,当球碰到桌面时,以对称的角度弹回,判断球碰撞多少次后第一次回到入射点,若不能回去,则输出 -1。
思路:
设碰撞了 \(n\) 次,总共走了 \(k\) 圈。
可以得到公式,\(n * 2 * \alpha == k * 360\),答案就是 \(n - 1\),\(n = \frac{k * 180 * b}{a}\),又因为求的是第一次回到起点的碰撞次数,所以将 180 * \(b\) 和 \(a\) 进行一个约分,再令 \(k = a\),就解出答案了。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
LL T, a, b;
void solve(){
scanf("%lld%lld", &a, &b);
LL t = __gcd(a, 180 * b);
cout << 180 * b / t - 1 << "\n";
}
int main(){
cin >> T;
while (T--)
solve();
return 0;
}
M. 810975
题目大意:
进行 \(n\) 场比赛,赢了 \(m\) 场,其中最长连胜 \(k\) 场,问有几种可能的情况。
思路:
答案就是最长连胜场数大于等于 \(k\) 的方案减去最长连胜场数大于等于 \(k - 1\) 的方案。
转化一下,最长连胜场数大于等于 \(k\) 的方案数 = 所有方案数 - 至少有 1 个连胜场数大于 \(k\) 的方案数 + 至少有 2 个连胜场数大于 \(k\) 的方案数...
后面的也转化一下,然后求出答案。
参考题解:https://blog.csdn.net/uni_xxxyd/article/details/123166920
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 1e5 + 7, mod = 998244353;
LL n, m, k, s[N], sv[N];
LL qp(LL a, LL b){
LL ans = 1;
while (b){
if (b & 1) ans = ans * a % mod;
b >>= 1;
a = a * a % mod;
}
return ans;
}
LL inv(LL x){
return qp(x, mod - 2);
}
void init(){
s[0] = 1;
for (int i = 1; i <= N - 7; ++ i)
s[i] = s[i - 1] * i % mod;
sv[N - 7] = inv(s[N - 7]);
for (int i = N - 8; i >= 0; -- i)
sv[i] = sv[i + 1] * (i + 1) % mod;
}
LL C(LL a, LL b){
return s[a] * sv[b] % mod * sv[a - b] % mod;
}
LL solve(LL x){
LL ans = 0;
for (LL i = 1; i * x <= m; ++ i){
if (i & 1) ans = ( ans + C(n - m + 1, i) * C(n - i * x, n - m) % mod ) % mod;
else ans = ( ans - C(n - m + 1, i) * C(n - i * x, n - m) % mod + mod ) % mod;
}
return ans;
}
int main(){
cin >> n >> m >> k;
if (k == 0){
cout << (m == 0) << "\n";
return 0;
}
init();
cout << ( solve(k) - solve(k + 1) + mod ) % mod << "\n";
return 0;
}