2024 暑假友誼賽 3
A - A
CodeForces - 1187E
思路
設 \(f_i\) 表示以 \(i\) 為根的子樹產生的貢獻,則有 \(f_i=size_i+\sum\limits_{j\in son_i} f_j\),即起初選定 \(i\) 為起點後產生 \(size_i\) 的貢獻,後續是它的子樹產生的貢獻。
但這樣以不同根節點去求貢獻是 \(O(n^2)\) 的,所以考慮換根 dp。
設 \(dp_i\) 表示為以 \(i\) 為根的答案。
以上圖為例,設整棵樹大小為 \(n\) ,我們要將 \(x\) 的答案換根到 \(y\) 上:
即換根 dp 最終轉移方程。
程式碼
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector g(n + 1, vector<int>());
for (int i = 1; i < n; i ++) {
int u, v;
cin >> u >> v;
g[u].emplace_back(v);
g[v].emplace_back(u);
}
vector<i64> f(n + 1), dp(n + 1), siz(n + 1);
auto dfs = [&](auto && self, int u, int fa)->void{
siz[u] = 1;
for (auto v : g[u]) {
if (v == fa) continue;
self(self, v, u);
siz[u] += siz[v];
f[u] += f[v];
}
f[u] += siz[u];
};
dfs(dfs, 1, 0);
i64 ans = 0;
ans = dp[1] = f[1];
auto dpdfs = [&](auto && self, int u, int fa)->void{
if (u != 1) {
dp[u] = dp[fa] + n - 2 * siz[u];
ans = max(ans, dp[u]);
}
for (auto v : g[u]) {
if (v == fa) continue;
self(self, v, u);
}
};
dpdfs(dpdfs, 1, 0);
cout << ans << '\n';
return 0;
}
B - B
CodeForces - 977D
思路
很經典的一道題,把能被 \(3\) 整除或者能 \(× 2\) 得到的數看成由 \(x\) 到 \(y\) 的一條有向邊,這樣就轉換成了 \(DAG\) 模型,要求一條長度為 \(n\) 的變化方案,其實就是這個模型上的最長路,上拓撲序即可。
程式碼
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<i64> a(n + 1);
for (int i = 1; i <= n; i ++)
cin >> a[i];
vector<int> in(n + 1);
vector g(n + 1, vector<int>());
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= n; j ++) {
if (j == i) continue;
if (a[i] * 2 == a[j]) {
g[i].push_back(j);
in[j] ++;
}
if (a[i] % 3 == 0 && a[i] / 3 == a[j]) {
g[i].push_back(j);
in[j] ++;
}
}
}
queue<int> Q;
for (int i = 1; i <= n; i ++) {
if (!in[i]) {
Q.push(i);
}
}
vector<int> ans;
while (Q.size()) {
auto u = Q.front();
Q.pop();
ans.push_back(u);
for (auto v : g[u]) {
if (!--in[v]) {
Q.push(v);
}
}
}
for (auto i : ans)
cout << a[i] << " \n"[i == ans.back()];
return 0;
}
C - C
CodeForces - 1368D
思路
注意到選擇的 \(x\) 和 \(y\) 會變成兩個數 \(x\& y\) 和 \(x|y\),其實以二進位制的角度來看,就是這兩個數的對應位上的 \(1\) 發生了轉移,但是總共的 \(1\) 的個數未變,題目要求 \(a_i^2\),則 \(a_i\) 應該越大對答案的貢獻才會越大,所以存下每個數對應位上的 \(1\) 有多少個,貪心地去湊出最大的數即可。
程式碼
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<i64> a(n + 1), cnt(30);
for (int i = 1; i <= n; i ++) {
cin >> a[i];
for (int j = 0; j < 20; j ++) {
if (a[i] >> j & 1)
cnt[j] ++;
}
}
i64 ans = 0;
for (int i = 1; i <= n; i ++) {
int x = 0;
for (int j = 0; j < 20; j ++) {
if (cnt[j]) {
x += 1 << j;
cnt[j] --;
}
}
ans += 1ll * x * x;
}
cout << ans << '\n';
return 0;
}
D - D
AtCoder - arc082_b
思路
當 \(p_i=i\) 時,那它和旁邊的數交換一定可以使得兩邊的數都不等於其下標,所以遍歷一遍,碰到 \(p_i=i\) 的直接和旁邊的數交換一下記錄答案即可。
程式碼
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<int> p(n + 1);
for (int i = 1; i <= n; i ++) {
cin >> p[i];
}
int ans = 0;
for (int i = 1; i <= n; i ++) {
if (p[i] != i) continue;
if (i + 1 <= n)
swap(p[i], p[i + 1]);
else
swap(p[i], p[i - 1]);
ans ++;
}
cout << ans << '\n';
return 0;
}
E - E
CodeForces - 794C
思路
一道細節題。
貪心的思路是排序後 \(a\) 和 \(b\) 依次把最小字母和最大字母往前面填,但當 \(S_a>S_b\) 的時候,\(a,b\) 往前填反而會成全對方,所以這個時候得往後填。
寫法不同對細節的處理有不同。
程式碼
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
string a, b;
cin >> a >> b;
sort(a.begin(), a.end());
sort(b.begin(), b.end(), greater<>());
int n = a.size();
string ans1 = "", ans2 = "";
while (a.size() > (n + 1) / 2) a.pop_back();
while (b.size() > (n) / 2) b.pop_back();
while (n--) {
if (a[0] < b[0]) {
ans1 += a[0];
a.erase(a.begin());
} else {
ans2 += a.back();
a.pop_back();
}
if (n) {
if (a[0] < b[0]) {
ans1 += b[0];
b.erase(b.begin());
} else {
ans2 += b.back();
b.pop_back();
}
n--;
}
}
reverse(ans2.begin(), ans2.end());
cout << ans1 + ans2 << '\n';
return 0;
}
F - F
CodeForces - 1076E
思路
這幾天有點魔怔了,看到子樹 \((u,v)\) 對什麼的老是想到樹上啟發式,唉,杭電害得。
對 \(x\) 子樹中距離小於等於 \(k\) 的點全都加上一個值 \(x\),假設這棵樹只有一條鏈,那很顯然,答案其實就是做一個差分,然後跑一個字首和。
現在是多條鏈,但是這多條鏈上的點都需要加上 \(x\),那麼不妨將深度看成一個序列,在深度上進行差分,用 dfs 跑字首和,這樣就完成了樹上差分以及區間求值的操作。
需要注意的是,因為一棵樹有不同的子樹,也就是有許多不同的鏈,在對當前子樹做完差分的操作,回溯的時候要記得還原,否則會影響其他子樹的答案。
程式碼
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector g(n + 1, vector<int>());
for (int i = 1; i < n; i ++) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
int m;
cin >> m;
vector Q(n + 1, vector<pair<int, int>>());
while (m--) {
int v, d, x;
cin >> v >> d >> x;
Q[v].emplace_back(d, x);
}
vector<i64> pre(n + 1), ans(n + 1);
auto dfs = [&](auto && self, int u, int fa, int dep, i64 sum)->void{
for (auto &[d, x] : Q[u]) {
pre[dep] += x;
int to = dep + d + 1;
if (to <= n) {
pre[to] -= x;
}
}
sum += pre[dep];
ans[u] = sum;
for (auto v : g[u]) {
if (v == fa) continue;
self(self, v, u, dep + 1, sum);
}
for (auto &[d, x] : Q[u]) {
pre[dep] -= x;
int to = dep + d + 1;
if (to <= n) {
pre[to] += x;
}
}
};
dfs(dfs, 1, 0, 0, 0);
for (int i = 1; i <= n; i ++)
cout << ans[i] << " \n"[i == n];
return 0;
}
G - G
CodeForces - 611D
思路
考慮 dp。
設 \(dp_{i,j}\) 表示以從 \(j\) 到 \(i\) 構成的數字(以下稱做\(num_{i,j}\))作為結尾的方案數。
設 \(L=i-j\) 表示為該數字的長度,那麼顯然,大於這個長度的一定不可能轉移過來,而小於這個長度的字串構成的數字也一定小於 \(num_{i,j}\),那麼這一步得到的轉移方程為:
這一步可以用字首和最佳化。
接下來就是考慮相等的情況,相等的情況下可以透過 \(LCP(\text{最長公共字首})\) 的 \(n^2\) 演算法預處理,然後判斷 \(lcp\) 後的第一位大小情況即可,也可以透過 \(Sam\) 演算法等。
我這裡採用的是 二分+Hash 的做法(也有倍增+Hash),會比 \(n^2\) 的做法多一個 \(log\),總複雜度 \(O(n^2logn)\)
程式碼
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
struct Hash {
using u64 = unsigned long long;
u64 base = 13331;
vector<u64> pow, hash;
Hash(string &s) {
int N = s.size();
pow.resize(N + 1), hash.resize(N + 1);
pow[0] = 1, hash[0] = 0;
for (int i = 1; i < s.size(); i ++) {
pow[i] = pow[i - 1] * base;
hash[i] = hash[i - 1] * base + s[i];
}
}
u64 get(int l, int r) {
return hash[r] - hash[l - 1] * pow[r - l + 1];
}
//拼接兩個子串
u64 link(int l1, int r1, int l2, int r2) {
return get(l1, r1) * pow[r2 - l2 + 1] + get(l2, r2);
}
bool same(int l1, int r1, int l2, int r2) {
return get(l1, r1) == get(l2, r2);
}
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
string s;
cin >> s;
s = " " + s;
Hash hash(s);
auto check = [&](int x, int y)->bool{
int l = 0, r = y - x, ans = 0;
while (l <= r) {
int mid = l + r >> 1;
if (hash.same(x, x + mid - 1, y, y + mid - 1)) l = mid + 1, ans = mid;
else r = mid - 1;
}
if (ans == y - x) return false;
return s[ans + x] < s[ans + y];
};
vector dp(n + 1, vector<i64>(n + 1));
vector sum(n + 1, vector<i64>(n + 1));
for (int i = 1; i <= n; i ++)
dp[i][1] = 1;
const i64 mod = 1e9 + 7;
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= i; j ++) {
if (s[j] == '0') continue;
int k = max(1, j - (i - j));
(dp[i][j] += (sum[j - 1][j - 1] - sum[j - 1][k - 1] + mod) % mod) %= mod;
k --;
if (k >= 1 && check(k, j)) {
(dp[i][j] += dp[j - 1][k]) %= mod;
}
}
for (int j = 1; j <= i; j ++)
sum[i][j] = (sum[i][j - 1] + dp[i][j]) % mod;
}
i64 ans = 0;
for (int i = 1; i <= n; i ++)
ans = (ans + dp[n][i]) % mod;
cout << ans << '\n';
return 0;
}
H - H
CodeForces - 1416B
思路
要獲得均分首先得保證 \(Sum_a\bmod n=0\)。
考慮一種做法就是,首先將所有的數都彙集到 \(a_1\) 上,然後其他數就是 \(0\) 了,然後由 \(a_1\) 統一分配 \(Avg\) 平均數給其他 \(n-1\) 個數,這樣的做法有 \(2\times (n-1)\) 次操作。
期間會有一些數會產生餘數,那麼不妨讓 \(a_1\) ‘借’點數給它使得被 \(i\) 整除,然後又一併還給 \(a_1\),題目保證 \(1\le a_i\le 1e5\),所以最開始一定有 \(1\) 去彌補 \(i=2\) 的餘數,然後 \(a_2\) 把所有數給 \(a_1\) 後,又能保證有一定的數彌補 \(i=3 \dots\) 這樣的操作最多也就 \((n-1)\) 次,所以總次數不會超過 \(3(n-1)\),滿足題目要求。
程式碼
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
void solve() {
int n;
cin >> n;
i64 sum = 0;
vector<int> a(n + 1);
for (int i = 1; i <= n; i ++) {
cin >> a[i];
sum += a[i];
}
if (sum % n != 0) {
cout << -1 << '\n';
return ;
}
int avg = sum / n;
vector<array<int, 3>> ans;
for (int i = 2; i <= n; i ++) {
int x;
if (a[i] % i != 0) {
x = i - a[i] % i;
a[1] -= x;
a[i] += x;
ans.push_back({1, i, x});
}
x = a[i] / i;
a[1] += a[i];
a[i] = 0;
ans.push_back({i, 1, x});
}
for (int i = 2; i <= n; i ++) {
ans.push_back({1, i, avg - a[i]});
}
cout << ans.size() << '\n';
for (auto [a, b, c] : ans) {
cout << a << ' ' << b << ' ' << c << '\n';
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}