AtCoder Regular Contest 116 补题(C、D、E)
C - Multiple Sequences
题意:
给定数字和,问有多少个长度为的序列,满足,且是的倍数
思路:
由于序列是成倍变大的,所以序列肯定是一个非严格单调递增的序列,那么可以枚举最后一个数,通过递推可知,前面的数都是的约数,所以就相当于将的质因数拆解后,往前面个空放,对于每个因子来说都是独立的,所以质因数分解后,当前这个因子就是,当前因子为,由于因子是可不选的,所以相当于有个空(个是用来代表空放的),有个球,所以放的方案数就是,对于当前枚举的数,,然后把所有枚举的情况累加即答案
View Code
#include <bits/stdc++.h> using namespace std; #define int long long const int N = 4e5 + 10; const int mod = 998244353; int fact[N], infact[N]; int n, m; int lowbit(int x) { return x & -x; } int qmi(int a, int k) { int res = 1; while (k) { if (k & 1) res = res * a % mod; k >>= 1; a = a * a % mod; } return res; } void init() { fact[0] = infact[0] = 1; for (int i = 1; i < N; i++) { fact[i] = fact[i - 1] * i % mod; } infact[N - 1] = qmi(fact[N - 1], mod - 2) % mod; for (int i = N - 2; i >= 1; i--) { infact[i] = infact[i + 1] * (i + 1) % mod; } } int C(int n, int m) { if (m > n) return 0; return fact[n] * infact[m] % mod * infact[n - m] % mod; } signed main() { init(); cin >> n >> m; int res = 0; for (int i = 1; i <= m; i++) { int now = i; int temp = 1; for (int j = 2; j <= now / j; j++) { if (now % j == 0) { int cnt = 0; while (now % j == 0) { now /= j; cnt++; } temp = temp * C(n + cnt - 1, n - 1) % mod; temp %= mod; } } if (now > 1) temp = temp * C(n, n - 1) % mod; temp %= mod; res = (res + temp) % mod; } cout << res << endl; }
D - I Wanna Win The Game
题意:
给定和,现在问你有多少个序列满足下面的条件。,,
思路:
有异或操作,考虑将分解成二进制数来考虑,由于所有数的异或和为,则说明对于的每一位,都必须要有偶数个,保证异或为,那么对于每一位就可以考虑有多少个,每一位有个1,也就是说每一位的大小都可以是,这就相当于是分组背包,每个组可以挑选体积为的物品,此时定义定义为考虑分解成二进制后的前位,体积为的方案数,那么最终答案就是,转移方程为,相当于选出这些后,分给个空,方程可以优化前一维,最终输出就是答案
View Code
#include <bits/stdc++.h> using namespace std; #define int long long const int N = 5010; const int mod = 998244353; int fact[N], infact[N]; int n, m; int f[N]; int qmi(int a, int k) { int res = 1; while (k) { if (k & 1) res = res * a % mod; k >>= 1; a = a * a % mod; } return res; } void init() { fact[0] = infact[0] = 1; for (int i = 1; i < N; i++) { fact[i] = fact[i - 1] * i % mod; } infact[N - 1] = qmi(fact[N - 1], mod - 2) % mod; for (int i = N - 2; i >= 1; i--) { infact[i] = infact[i + 1] * (i + 1) % mod; } } int C(int n, int m) { if (m > n) return 0; return fact[n] * infact[m] % mod * infact[n - m] % mod; } signed main() { init(); cin >> n >> m; f[0] = 1; for (int i = 0; (1 << i) <= m; i++) { for (int j = m; j >= 0; j--) { for (int k = 1; 2 * k * (1 << i) <= j; k++) { if (j >= 2 * k * (1 << i)) { f[j] += f[j - 2 * k * (1 << i)] * C(n, 2 * k) % mod; f[j] %= mod; } } } } cout << f[m] << endl; }
E - Spread of Information
题意:
给定个点的的树,现在可以选个点感染病毒,病毒以每秒一个点的速度向周边感染,问通过放置病毒,感染整棵树的最短时间为多少?
思路:
可以先把问题转换成给定时间,来感染全图,最少需要多少病毒?可以发现时间是具有单调性的,所以是可以二分的,所以解决上面的问题,时间就可以用二分来枚举出来。给定时间来算感染全图需要的最少病毒数量,可以通过类树形搜索来解决
定义为在以为子树的结点中,离它最近病毒的距离
在以为子树的结点中,离它最远的未被感染的结点的距离
若,说明以为子树的所有结点都被感染了,所以当前点也会被感染,所以
若,距离当前点最远的未被感染的点的距离已经到了,此时不放病毒的话,就会导致那个最远的点在限定时间内被感染,所以当前点必须被感染,
最后回溯到根节点,若的话,说明病毒还没在时间内感染根结点,必须在根节点放置一个病毒
View Code
#include <bits/stdc++.h> using namespace std; const int N = 2e5 + 10; const int inf = 0x3f3f3f3f; #define int long long vector<int> e[N]; int n, k; int f[N], g[N]; int res; void dfs(int u, int fa, int limit) { f[u] = inf, g[u] = 0; for (auto v : e[u]) { if (v == fa) continue; dfs(v, u, limit); f[u] = min(f[u], f[v] + 1); g[u] = max(g[u], g[v] + 1); } if (f[u] + g[u] <= limit) { g[u] = -inf; } else if (g[u] == limit) { f[u] = 0, g[u] = -inf; res++; } } bool check(int x) { res = 0; dfs(1, -1, x); if (g[1] >= 0) res++; return res <= k; } signed main() { cin >> n >> k; for (int i = 1, u, v; i < n; i++) { cin >> u >> v; e[u].push_back(v); e[v].push_back(u); } int l = 0, r = n; while (l < r) { int mid = (l + r) >> 1; if (check(mid)) { r = mid; } else { l = mid + 1; } } cout << l << endl; }