Contest Info
[Practice Link](https://cn.vjudge.net/contest/313014)
Solved | A | B | C | D | E | F | G | H | I | J | K |
---|---|---|---|---|---|---|---|---|---|---|---|
9/11 | O | - | Ø | O | - | O | O | O | O | O | O |
- O 在比賽中通過
- Ø 賽后通過
- ! 嘗試了但是失敗了
- - 沒有嘗試
Solutions
A. Cotree
題意:
有兩棵樹,一共有\(n\)個點,現在要求在兩棵樹中連一條邊,使得他們變成一棵樹,如何連邊使下式最小:
思路:
我們可以單獨考慮邊的貢獻,一條邊的貢獻是這條邊兩邊的點數乘積。
那么新加的邊的貢獻是固定的。
我們考慮怎么減少已存在的邊的貢獻,其實我們可以感性的理解一下,我們最后選出的兩個點相連,我們強制讓它們成為他們各自樹中的根,那么這么考慮:
- 令\(f[i]\)表示以\(i\)為根的子樹中的邊的貢獻
那么轉移有:
- 令\(g[i]\)表示以\(i\)為根的非子樹中的邊的貢獻
那么轉移有:
其中\(S\)代表另外一棵樹的大小,最后那兩部分主要是\(u \rightarrow fa[u]\)那條邊的貢獻變了
代碼:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define N 100010
int n, S, T;
vector <vector<int>> G;
int fa[N], sze[N];
ll f[N], g[N];
void DFS(int u) {
sze[u] = 1;
f[u] = 0;
for (auto v : G[u]) if (v != fa[u]) {
fa[v] = u;
DFS(v);
sze[u] += sze[v];
f[u] += f[v];
f[u] += 1ll * sze[v] * (n - sze[v]);
}
}
ll DFS2(int u, int rt, int S) {
if (u != rt) {
g[u] = g[fa[u]] + f[fa[u]] - f[u] + 1ll * (n - sze[u] - S) * (sze[u] + S) - 1ll * sze[u] * (n - sze[u]);
} else {
g[u] = 0;
}
ll res = f[u] + g[u];
for (auto v : G[u]) if (v != fa[u]) {
res = min(res, DFS2(v, rt, S));
}
return res;
}
int main() {
while (scanf("%d", &n) != EOF) {
G.clear(); G.resize(n + 1);
for (int i = 1; i <= n; ++i) fa[i] = -1;
for (int i = 1, u, v; i < n - 1; ++i) {
scanf("%d%d", &u, &v);
G[u].push_back(v);
G[v].push_back(u);
}
int rt[2]; rt[0] = 1;
fa[1] = 1;
DFS(1); S = sze[1]; T = n - S;
for (int i = 1; i <= n; ++i) {
if (fa[i] == -1) {
rt[1] = i;
fa[i] = i;
DFS(i);
break;
}
}
ll res = DFS2(1, 1, T) + DFS2(rt[1], rt[1], S) + 1ll * S * T;
printf("%lld\n", res);
}
return 0;
}
C.Trap
題意:
有\(n\)根長度為\(a_i\)的棍子,詢問有多少種方案選出四條邊,使得它們的\(gcd = 1\),並且能夠組成一個等腰梯形。
思路:
考慮\(f(d)\)為長度為\(d\)的倍數的棍子組成的方案數,那么可以容斥原理有:
其中\(\mu(i)\)為莫比烏斯函數,它的定義如下:
根據\(\mu(n)\)的定義,我們發現當\(n\)中奇數個質因子的時候\(\mu(n) = -1\),當\(n\)中有偶數個質因子的時候\(\mu(n) = 1\),恰好是我們需要的容斥系數。
考慮怎么求解\(f(d)\),可以兩個循環枚舉上底和腰:
- 下底的范圍是(上底, 上底 + 腰 * 2)
- 注意特判下底等於腰的情況和上底等於腰的情況,棍子是否足夠
為什么下底的下界是上底 + 腰 * 2 ?
考慮等腰的那個等角越小的時候,下底越長,那么極限情況,就是角度為\(0\)的時候,這時候長度就是上底 + 腰 * 2, 但是這個長度不合法,所以是開區間。
代碼:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define M 10010
int n, a[M], cnt[M];
vector <vector<int>> vec, fac;
int prime[M], check[M], mu[M], tot;
void init() {
tot = 0;
memset(check, 0, sizeof check);
mu[1] = 1;
fac.clear();
fac.resize(M);
for (int i = 2; i < M; ++i) {
if (!check[i]) {
prime[++tot] = i;
mu[i] = -1;
}
for (int j = 1; j <= tot; ++j) {
if (1ll * i * prime[j] >= M) break;
check[i * prime[j]] = 1;
if (i % prime[j] == 0) {
mu[i * prime[j]] = 0;
break;
} else {
mu[i * prime[j]] = -mu[i];
}
}
}
for (int i = 1; i < M; ++i) {
for (int j = i; j < M; j += i) {
fac[j].push_back(i);
}
}
}
int main() {
init();
while (scanf("%d", &n) != EOF) {
vec.clear(); vec.resize(M);
memset(cnt, 0, sizeof cnt);
for (int i = 1; i <= n; ++i) {
scanf("%d", a + i);
++cnt[a[i]];
for (auto it : fac[a[i]]) {
vec[it].push_back(a[i]);
}
}
ll res = 0;
for (int i = 1; i < M; ++i) {
if (vec[i].empty() || mu[i] == 0) continue;
ll tmp = 0;
sort(vec[i].begin(), vec[i].end());
vec[i].erase(unique(vec[i].begin(), vec[i].end()), vec[i].end());
int sze = vec[i].size();
for (auto it : vec[i]) { //枚舉上邊
int l = 0, r = 0;
while (l < sze && vec[i][l] < it + 1) ++l;
for (auto it2 : vec[i]) { //枚舉腰
if (it != it2 && cnt[it2] >= 2) {
while (r < sze - 1 && vec[i][r + 1] < it + 2 * it2) ++r;
if (l < sze && r < sze && l <= r) {
tmp += r - l + 1;
if (vec[i][l] <= it2 && it2 <= vec[i][r]) {
if (cnt[it2] < 3) --tmp;
}
}
} else if (it == it2 && cnt[it] >= 3) {
while (r < sze - 1 && vec[i][r + 1] < it + 2 * it2) ++r;
if (l < sze && r < sze && l <= r) {
tmp += r - l + 1;
}
}
}
}
res += tmp * mu[i];
}
printf("%lld\n", res);
}
return 0;
}
D.Wave
題意:
找一個最長的'wave',要求滿足以下限制:
- 序列長度大於等於\(2\)
- 所有奇數位置上的數相同
- 所有偶數位置上的數相同
- 奇數位置和偶數位置的數不同
思路:
因為值域只有\([1, 100]\),那么暴力枚舉奇數位置上的數和偶數位置上的數即可。
時間復雜度\(\mathcal{O}(nc)\)
代碼:
#include <bits/stdc++.h>
using namespace std;
#define N 100010
int n, c, a[N];
int f[N][110], nx[110];
int main() {
scanf("%d%d", &n, &c);
for (int i = 1; i <= n; ++i) scanf("%d", a + i);
for (int i = 1; i <= c; ++i) nx[i] = n + 1, f[n + 1][i] = n + 1;
for (int i = n; i >= 0; --i) {
for (int j = 1; j <= c; ++j) {
f[i][j] = nx[j];
}
if (i) nx[a[i]] = i;
}
int res = 0;
for (int i = 1; i <= c; ++i) {
for (int j = 1; j <= c; ++j) if (i != j) {
int tmp = 0;
int it = 0;
while (it <= n) {
it = f[it][i];
if (it <= n) {
tmp += 1;
} else break;
it = f[it][j];
if (it <= n) {
tmp += 1;
} else break;
}
if (tmp > 1) {
res = max(res, tmp);
}
}
}
printf("%d\n", res);
return 0;
}
F.String
題意:
給出一個字符串,只包含'a', 'v', 'i', 'n', 要求從中等概率可重復的取出四個字符,問恰好是'avin'的概率是多少。
思路:
根據乘法原理計算即可。
代碼:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define N 110
int n;
char s[N];
int cnt[220];
int main() {
while (scanf("%d", &n) != EOF) {
scanf("%s", s + 1);
memset(cnt, 0, sizeof cnt);
for (int i = 1; i <= n; ++i) ++cnt[s[i]];
if (!cnt['a'] || !cnt['v'] || !cnt['i'] || !cnt['n']) {
puts("0/1");
} else {
ll a = 1ll * cnt['a'] * cnt['v'] * cnt['i'] * cnt['n'];
ll b = 1ll * n * n * n * n;
ll G = __gcd(a, b);
a /= G;
b /= G;
printf("%lld/%lld\n", a, b);
}
}
return 0;
}
G. Traffic
題意:
在一個十字路口,有\(n\)輛東西走向的車,他們會在\(a_i\)時刻到達,有\(m\)輛南北走向的車,他們會在\(b_i\)時刻到達。問需要讓\(m\)輛南北走向的車整體等待多少秒,使得他們的開始行動之后不會和東西走向的車相撞?
思路:
考慮\(a_i\)和\(b_i\)只有\(1000\),那么等待時間不會超過\(1000\),暴力枚舉即可。
代碼:
#include <bits/stdc++.h>
using namespace std;
#define N 5100
int n, m, a[N], b[N];
bool ok(int x) {
for (int i = 1; i <= m; ++i) {
if (a[b[i] + x]) {
return 0;
}
}
return 1;
}
int main() {
while (scanf("%d%d", &n, &m) != EOF) {
memset(a, 0, sizeof a);
for (int i = 1, x; i <= n; ++i) {
scanf("%d", &x);
a[x] = 1;
}
for (int i = 1; i <= m; ++i) scanf("%d", b + i);
for (int i = 0; i <= 2000; ++i) {
if (ok(i)) {
printf("%d\n", i);
break;
}
}
}
return 0;
}
H.Rng
題意:
考慮隨機選擇一個區間的過程:
- 先從\([1, n]\)等概率選擇\(r\)
- 再在\([1, r]\)中等概率選擇出\(l\)
問隨機選擇兩個區間,它們相交的概率?
思路:
考慮枚舉\(r\):
- 考慮\(R\)落在\([r + 1, n]\)的概率,即為:
- 考慮\(R\)落在\([l, r]\)之間的概率,即為:
代碼:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define N 1000010
const ll p = 1e9 + 7;
ll qmod(ll base, ll n) {
ll res = 1;
while (n) {
if (n & 1) {
res = res * base % p;
}
base = base * base % p;
n >>= 1;
}
return res;
}
int n;
ll f[N], g[N], inv[N];
ll Sf(int l, int r) {
if (l > r) return 0;
return (f[r] - f[l - 1] + p) % p;
}
ll Sg(int l, int r) {
if (l > r) return 0;
return (g[r] - g[l - 1] + p) % p;
}
void add(ll &x, ll y) {
x += y;
if (x >= p) x -= p;
}
int main() {
inv[1] = 1;
for (int i = 2; i < N; ++i) inv[i] = inv[p % i] * (p - p / i) % p;
for (int i = 1; i < N; ++i) {
f[i] = (f[i - 1] + i) % p;
g[i] = (g[i - 1] + inv[i]) % p;
}
while (scanf("%d", &n) != EOF) {
ll res = 0;
for (int i = 1; i <= n; ++i) {
add(res, 1ll * i * inv[n] % p * inv[n] % p * Sg(i + 1, n) % p);
add(res, 1ll * inv[i] * inv[n] % p * inv[n] % p * (1ll * i * (i + 1) % p - Sf(1, i) + p) % p);
}
printf("%lld\n", res);
}
return 0;
}
I. Budget
簽到題。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define N 1100
int n;
char s[N];
ll get() {
scanf("%s", s + 1);
int len = strlen(s + 1);
int pos = -1;
for (int i = 1; i <= len; ++i) {
if (s[i] == '.') {
pos = i;
break;
}
}
if (pos == -1 || len - pos < 3) return 0;
int num = s[pos + 3] - '0';
if (num <= 4) return -num;
else return 10 - num;
}
int main() {
while (scanf("%d", &n) != EOF) {
ll res = 0;
for (int i = 1; i <= n; ++i) {
res += get();
}
printf("%.3f\n", res * 0.001);
}
return 0;
}
J. Worker
題意:
有\(n\)個銷售點,有\(m\)個銷售員,每個銷售員在第\(i\)個銷售點會產生\(a_i\)的訂單,問如何分配銷售員使得每個銷售點產生的訂單相同。
思路:
首先單個銷售點的訂單量肯定是所有銷售點\(a_i\)的最小公倍數的倍數。
那么判斷\(m\)能否整除其最小公倍數即可,然后按比例分配。
代碼:
#include <bits/stdc++.h>
using namespace std;
#define N 1100
#define ll long long
int n, a[N];
ll m, lcm;
void solve() {
ll one = 0;
for (int i = 1; i <= n; ++i) {
one += lcm / a[i];
}
if (m % one) {
puts("No");
return;
}
ll cur = m / one;
puts("Yes");
for (int i = 1; i <= n; ++i) printf("%lld%c", cur * (lcm / a[i]), " \n"[i == n]);
}
int main() {
while (scanf("%d%lld", &n, &m) != EOF) {
for (int i = 1; i <= n; ++i) scanf("%d", a + i);
lcm = a[1];
for (int i = 2; i <= n; ++i) {
lcm = lcm * a[i] / __gcd(lcm, 1ll * a[i]);
}
solve();
}
return 0;
}
K. Class
題意:
給出:
計算\(a \cdot b\)。
思路:
簽到題。
代碼:
#include <bits/stdc++.h>
using namespace std;
int main() {
int x, y;
while (cin >> x >> y) {
int a = (x + y) / 2;
int b = (x - y) / 2;
cout << a * b << "\n";
}
return 0;
}