A - Sasha and Array Coloring (CF1843 A)
題目大意
給定一個陣列,給每個元素涂色,求最大的代價,
代價為每個顏色的代價和,
每個顏色的代價為涂了該顏色的元素的極差,
解題思路
因為是極差,每個元素要么對答案有正的貢獻,要么有負的貢獻,要么無貢獻,且正負貢獻的個數要相同,
因為要最大值,自然就是想有正貢獻的是最大的那些數,負貢獻的是最小的那些數,
因此答案就是最大的那一半的和\(-\)最小的那一半的和,奇數的話中間多出來的一個無貢獻,
神奇的代碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int t;
cin >> t;
while(t--){
int n;
cin >> n;
vector<int> a(n);
for(auto &i : a)
cin >> i;
sort(a.begin(), a.end());
int ans = -accumulate(a.begin(), a.begin() + n / 2, 0) + accumulate(a.end() - n / 2, a.end(), 0);
cout << ans << '\n';
}
return 0;
}
B - Long Long (CF1843 B)
題目大意
給定一個陣列,可以進行一個操作,要求最小化運算元,使得陣列元素的和最大,
操作為:選定一個區間,對區間里的每個元素取其相反數,
解題思路
很顯然,和最大一定是將所有的負數變為正數,
然后考慮如何求最小運算元,
對于一連串的負數,我們肯定選擇這個區間,這樣操作一次就好了,
對于\(----+---\),如果我們一次選定這個區間,后續還需要一次操作將里面的 +變回來,其操作次數跟只操作其中的負區間的次數是一樣的,都是兩次,不會更優,
因此就對每個負區間進行操作,答案就是這里面負區間的個數了,
神奇的代碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int t;
cin >> t;
while(t--){
int n;
cin >> n;
vector<int> a(n);
for(auto &i : a)
cin >> i;
LL ans = 0;
for(auto &i : a)
ans += abs(i);
int cnt = 0;
bool neg = (a[0] < 0);
for(auto &i : a){
if (i > 0 && neg){
cnt ++;
neg = false;
}else if (i < 0)
neg = true;
}
cnt += neg;
cout << ans << ' ' << cnt << '\n';
}
return 0;
}
C - Sum in Binary Tree (CF1843 C)
題目大意
給定一棵完全二叉樹,點權為其標號,問從\(n\)號點開始,不斷往父親節點走,其點權和是多少,
解題思路
父親節點的標號為\(\frac{n}{2}\),因此直接模擬即可,走 \(\log\)次就走到根了,
神奇的代碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int t;
cin >> t;
while(t--){
LL n;
cin >> n;
LL ans = 0;
while(n){
ans += n;
n >>= 1;
}
cout << ans << '\n';
}
return 0;
}
D - Apple Tree (CF1843 D)
題目大意
給定一棵有根樹,樹上有兩個蘋果,每一時刻,每個蘋果可以往其中一個兒子節點移動,最終移動到葉子處,
設兩個蘋果最終所處的葉子節點為\(a,b\),問 \((a,b)\)的情況數,
解題思路
如果一個蘋果在\(x\)節點,那么它能移動的葉子處就是以 \(x\)為根的葉子節點,如果有 \(a\)個葉子,那么該蘋果的可移動到的葉子數就是 \(a\),
現在有兩個蘋果,假設它們最終可移動到的葉子節點數分別為\(a,b\),那最終的情況數就是 \(a \times b\),
因此事先預處理\(son[x]\)表示以\(x\)為根的葉子數量,\(O(1)\)回答即可,
神奇的代碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int t;
cin >> t;
while(t--){
int n;
cin >> n;
vector<vector<int>> edge(n);
for(int i = 1; i < n; ++ i){
int u, v;
cin >> u >> v;
-- u, -- v;
edge[u].push_back(v);
edge[v].push_back(u);
}
vector<int> son(n, 0);
function<void(int, int)> dfs = [&](int u, int fa){
bool leaf = true;
for(auto &v : edge[u]){
if (v == fa)
continue;
leaf = false;
dfs(v, u);
son[u] += son[v];
}
son[u] += leaf;
};
dfs(0, 0);
int q;
cin >> q;
while(q--){
int u, v;
cin >> u >> v;
-- u, -- v;
cout << 1ll * son[u] * son[v] << '\n';
}
}
return 0;
}
E - Tracking Segments (CF1843 E)
題目大意
給定一個有\(n\)個\(0\)的陣列\(a\),以及 \(m\)個區間,
一個區間如果是好區間,則其間 \(1\)的個數嚴格大于 \(0\)的個數,
現依次進行 \(q\)次操作,每次操作將 \(a_x = 1\),
問最早進行了多少次操作后,出現好區間,
解題思路
我們可以依次列舉\(q\),得到操作后的陣列,接下來就是判斷每個區間是不是 好區間,即判斷\(1\)的個數和 \(0\)的個數的大小關系,樸素判斷是\(O(nm)\),但我們可以事先對 \(1\)的個數求一遍陣列\(a\)的前綴和 ,這樣每個區間的判斷都可以在\(O(1)\) 得出結果,判斷的復雜度降為\(O(n + m)\),
由于我們是列舉\(q\)的,此時時間復雜度為 \(O(q(n + m))\),但可以發現 \(q\)與是否存在 好區間是一個單調的函式,即如果\(q=4\)存在 好區間,那么\(q > 4\)也一定存在好區間,因此我們可以二分這個 q,得到其臨界點,
最終時間復雜度降為\(O((n + m)\log q)\)
神奇的代碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int t;
cin >> t;
while(t--){
int n, m;
cin >> n >> m;
vector<array<int, 2>> seg(m);
for(auto &i : seg){
cin >> i[0] >> i[1];
-- i[0], -- i[1];
}
int q;
cin >> q;
vector<int> pos(q);
for(auto &i : pos){
cin >> i;
-- i;
}
auto check = [&](int x){
vector<int> cnt(n, 0);
for(int i = 0; i < x; ++ i)
cnt[pos[i]] = 1;
partial_sum(cnt.begin(), cnt.end(), cnt.begin());
for(auto &[l, r] : seg){
int one = cnt[r];
if (l > 0)
one -= cnt[l - 1];
int zero = r - l + 1 - one;
if (one > zero)
return true;
}
return false;
};
int l = 0, r = q;
while(l + 1 < r){
int mid = (l + r) >> 1;
if (check(mid))
r = mid;
else
l = mid;
}
if (!check(r))
r = -1;
cout << r << '\n';
}
return 0;
}
F1 - Omsk Metro (simple version) (CF1843 F1)
題目大意
給定一棵點權為\(1\) 或\(-1\)的樹,點數為\(n\),
給定\(q\)個詢問\(u, v, k\),依次回答從\(u \to v\)這條路徑中,是否存在一個子區間,其和為 \(k\),
該題\(u = 1\)
解題思路
子區間和可以轉換成兩個前綴和作差,那么在一般的問題里,我們可以列舉每個前綴和,若其值為\(x\),那就看之前的前綴和是否有值為 \(x - k\)的,
很顯然本題里,復雜度是\(O(nq)\),無法通過,但本題有個奇特點為點權僅為\(1\)或 \(-1\),這其間或許有什么性質,
容易發現,假設一個區間的最大欄位和為 \(max\),最小欄位和為 \(min\),那么 \([min, max]\)區間的數都可以取到,即都存在對應的子區間,因為點權僅為\(1\)或 \(-1\),一個子區間左右擴張,其值只會加一或者減一,不會跳躍,而初始值為 \(0\),
因此問題就轉換成維護一個區間的子區間的最大值和最小值即可,這是一個簡單的\(dp\),即設 \(dp[i]\)表示以 \(i\)結尾的最大后綴和,然后再維護一個全域的最大值,最小值同理,
由于本題中 \(u = 1\),因此可以直接從 \(1\)開始 \(dfs\),維護這個 \(dp\)即可,撤銷\(dp\)相當于回溯,
神奇的代碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int t;
cin >> t;
while(t--){
int n;
cin >> n;
vector<vector<int>> edge(n + 1);
vector<int> val(n + 1);
vector<vector<int>> target(n + 1);
vector<int> q;
val[0] = 1;
int cur = 1;
for(int i = 0; i < n; ++ i){
string op;
cin >> op;
if (op[0] == '+'){
int v, x;
cin >> v >> x;
-- v;
edge[v].push_back(cur);
edge[cur].push_back(v);
val[cur] = x;
++ cur;
}else{
int u, v, k;
cin >> u >> v >> k;
-- u, -- v;
assert(u == 0);
target[v].push_back(int(q.size()));
q.push_back(k);
}
}
vector<int> ans(q.size());
function<void(int, int, int, int, int, int)> dfs = [&](int u, int fa, int maxx, int minn, int gmaxx, int gminn){
for(auto &t : target[u]){
ans[t] = (q[t] <= gmaxx && q[t] >= gminn);
}
for(auto &v : edge[u]){
if (v == fa)
continue;
int nmaxx = max({maxx + val[v], val[v], 0});
int nminn = min({minn + val[v], val[v], 0});
int ngmaxx = max(gmaxx, nmaxx);
int ngminn = min(gminn, nminn);
dfs(v, u, nmaxx, nminn, ngmaxx, ngminn);
}
};
dfs(0, 0, 1, 0, 1, 0);
for(auto &i : ans)
if (i)
cout << "YES" << '\n';
else
cout << "NO" << '\n';
}
return 0;
}
F2 - Omsk Metro (hard version) (CF1843 F2)
題目大意
給定一棵點權為\(1\) 或\(-1\)的樹,點數為\(n\),
給定\(q\)個詢問\(u, v, k\),依次回答從\(u \to v\)這條路徑中,是否存在一個子區間,其和為 \(k\),
解題思路
本題\(u \neq 1\),
在上一題中,我們將問題轉換成維護一個區間的子區間的最大值和最小值,
注意到這個資訊是可合并的,以最大值為例,
假設我們有一個左區間和一個右區間,現在我們要將其合并成一個大區間,并求其子區間的最大值,
那么該最大值只有三種情況:
- 左區間的最大值
- 右區間的最大值
- 左區間的最大后綴和+右區間的最大前綴和
因此我們只需要額外增加最大前綴和和最大后綴和這兩個資訊,我們就可以將兩個區間合并,得到新區間的最大值,
而為了維護最大前綴和和最大后綴和,還需要區間和這個資訊,因為新區間的最大前綴和有兩種情況:
- 左區間的
最大前綴和 - 左區間的
區間和+右區間的最大前綴和
最大后綴和同理,因此我們只要維護一個區間的
- 最大值、最小值
- 最大前綴和、最小前綴和
- 最大后綴和、最小后綴和
- 區間和
這些資訊,我們就可以將兩個區間合并,得到一個大區間的資訊,
可合并有什么用呢?
每次詢問其實就是詢問一個區間的最大子區間和和最小子區間和,因為這些區間的數量級是\(O(n^2)\),我們不可能一一計算,而是通過只計算某些區間,這樣其他區間都可以通過這些區間,以較少的代價合并得到,
回想下 \(st\)表,它可以 \(O(1)\)回答出任意一個區間的最值,但它預處理的復雜度只有 \(O(n\log n)\),也就是說它可以通過計算 \(n\log n\)個區間的最值,然后以\(O(1)\)的代價通過 這些計算好的區間,合并出任意區間的最值(注意最值這個資訊也是可合并的),而它用到的方法就是倍增,
同樣,既然我們要維護的資訊是可合并的,我們可以運用倍增的思想,即樹上倍增,對于每個點,維護以這個點往父親方向,一共\(2^j\)個點所組成的序列的資訊,然后對于每個詢問所對應的路徑,通過 倍增的方式可以拆成\(\log\)個區間進行合并 ,
假設詢問\(u \to v\),因為在樹上,我們可以在它們的\(lca\)點拆開,這樣\(u \to lca\), \(v \to lca\)就是一條鏈,通過倍增合并得到其資訊(跟倍增求lca是一樣的),最后再將\(u \to lca\)和\(v \to lca\)合并(注意\(v \to lca\)資訊的前后綴要反轉過來)即得到\(u \to v\)的資訊,
這樣每次詢問的復雜度就是\(O(\log n)\)了,
當然因為是可合并的,樹鏈剖分將一個路徑剖分成若干條輕鏈和重鏈合并也不是不行
神奇的代碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
struct info{
int max, premax, sufmax;
int min, premin, sufmin;
int sum;
};
info operator+(const info& a, const info& b){
info c;
c.max = max({a.max, b.max, a.sufmax + b.premax});
c.premax = max(a.premax, a.sum + b.premax);
c.sufmax = max(b.sufmax, b.sum + a.sufmax);
c.min = min({a.min, b.min, a.sufmin + b.premin});
c.premin = min(a.premin, a.sum + b.premin);
c.sufmin = min(b.sufmin, b.sum + a.sufmin);
c.sum = a.sum + b.sum;
return c;
}
info reverse(const info& a){
info c = a;
swap(c.premax, c.sufmax);
swap(c.premin, c.sufmin);
return c;
}
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int t;
cin >> t;
while(t--){
int n;
cin >> n;
vector<int> val(n + 1);
val[0] = 1;
int cur = 1;
vector<array<int, 3>> q;
vector<vector<int>> fa(32, vector<int>(n + 1));
vector<int> deep(n + 1);
for(int i = 0; i < n; ++ i){
string op;
cin >> op;
if (op[0] == '+'){
int v, x;
cin >> v >> x;
-- v;
val[cur] = x;
fa[0][cur] = v;
deep[cur] = deep[v] + 1;
++ cur;
}else{
int u, v, k;
cin >> u >> v >> k;
-- u, -- v;
q.push_back({u, v, k});
}
}
vector<vector<info>> up(32, vector<info>(n));
for(int i = 0; i < n; ++ i){
if (val[i] == 1){
up[0][i] = info{1,1,1,0,0,0,1};
}else{
up[0][i] = info{0,0,0,-1,-1,-1,-1};
}
}
for(int i = 1; i < 32; ++ i){
for(int j = 0; j < n; ++ j){
fa[i][j] = fa[i - 1][fa[i - 1][j]];
if (fa[i - 1][j] != fa[i][j])
up[i][j] = up[i - 1][j] + up[i - 1][fa[i - 1][j]];
}
}
auto find = [&](int u, int v){
if (deep[u] < deep[v])
swap(u, v);
int dis = deep[u] - deep[v];
info l{}, r{};
for(int i = 0; i < 32; ++ i){
if (dis & 1){
l = l + up[i][u];
u = fa[i][u];
}
dis >>= 1;
}
if (u == v){
return l + reverse(up[0][u]);
}
for(int i = 31; i >= 0; -- i){
if (fa[i][u] != fa[i][v]){
l = l + up[i][u];
r = r + up[i][v];
u = fa[i][u];
v = fa[i][v];
}
}
int lca = fa[0][u];
l = l + up[0][u];
r = r + up[0][v];
return l + up[0][lca] + reverse(r);
};
auto solve = [&](int u, int v, int k){
auto res = find(u, v);
return res.min <= k && k <= res.max;
};
for(auto &[u, v, k] : q)
if (solve(u, v, k))
cout << "YES" << '\n';
else
cout << "NO" << '\n';
}
return 0;
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/555780.html
標籤:其他
上一篇:自然語言處理 Paddle NLP - 情感分析技術及應用SKEP-實踐
下一篇:返回列表
