Codeforces Raif Round 1 (Div. 1 + Div. 2)
A
#include <bits/stdc++.h> #define all(n) (n).begin(), (n).end() #define se second #define fi first #define pb push_back #define mp make_pair #define sqr(n) (n)*(n) #define rep(i,a,b) for(int i=(a);i<=(b);++i) #define per(i,a,b) for(int i=(a);i>=(b);--i) #define IOS ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr) using namespace std; typedef unsigned long long ull; typedef long long ll; typedef unsigned long long ull; typedef pair<int, int> PII; typedef pair<ll, ll> PLL; typedef vector<int> VI; typedef double db; template<class T1, class T2> bool umin(T1& a, T2 b) { return a > b ? (a = b, true) : false; } template<class T1, class T2> bool umax(T1& a, T2 b) { return a < b ? (a = b, true) : false; } template<class T> void clear(T& a) { T().swap(a); } const int N = 2e5 + 5; int n, m, _, k; ll a[N], b[N]; int main() { IOS; for (cin >> _; _; --_) { ll a, b, x, y; cin >> a >> b >> x >> y; ll ans = abs(x - a) + abs(y - b) + 2; if (x == a || y == b) ans -= 2; cout << ans << '\n'; } return 0; }
B
char s[N]; int main() { IOS; for (cin >> _; _; --_) { cin >> n >> s + 1; int a = 0; unordered_set<int> st; rep (i, 1, n) { if (s[i] == '-') st.insert(i % n), st.insert((i + 1) % n); else if (s[i] == '>') { if (a == 0) a = 1; else if (a == 2) a = -1; } else { if (a == 0) a = 2; else if (a == 1) a = -1; } } if (a != -1) cout << n << '\n'; else cout << st.size() << '\n'; } return 0; }
C
貪心模擬
int main() { IOS; for (cin >> _; _; --_) { string s; cin >> s; int x = 0, y = 0, ans = 0; bool f = 0; for (auto c : s) { if (c == 'A') { if (f && y) ++x, y = 0, f = 0; ++x; } else if (c == 'B') { if (x) --x, ++ans, f = 1; else { ++y; if (y == 2) ++ans, y = 0; } } } cout << s.size() - (ans << 1) << '\n'; } return 0; }
D
考慮到後效性, 倒著放, 同時對於行, 也是從 n 到 1, 把後效性消掉
我用的set只是為了圖形優美, 什麼容器都可
int a[N];
VI ans[N];
set<PII> st;
set<int> t;
int main() {
IOS; cin >> n; int res = 0; m = n;
rep(i, 1, n) cin >> a[i];
bool f = 1;
per(i, n, 1) {
if (a[i] == 0) continue;
if (a[i] == 1) ans[i].pb(m), ++res, st.insert({ m--, i });
else if (a[i] == 2) {
if (st.empty()) { f = 0; break; }
PII ls = *st.rbegin();
ans[i].pb(ls.fi); ++res;
st.erase(ls); t.insert(i);
}
else {
if (st.empty() && t.empty()) { f = 0; break; }
if (!t.empty()) {
int ls = *t.begin();
ans[i].pb(m); ++res;
ans[ls].pb(m--); ++res;
t.erase(ls); t.insert(i);
}
else {
PII ls = *st.rbegin();
ans[i].pb(m); ++res;
ans[ls.se].pb(m--); ++res;
st.erase(ls); t.insert(i);
}
}
}
if (!f || m < 0 || res > 2 * n) { cout << -1; return 0; }
cout << res << '\n';
rep(i, 1, n)
for (auto j : ans[i]) {
if (j < 0) cout << j - k + 1 << ' ';
else cout << j - k << ' ';
cout << i << '\n';
}
return 0;
}
E
優先佇列
注意到, 將一個胡蘿蔔分的快數越多, 花費的時間越少, 就發現是優先佇列, 每次選取 多分出一塊 省的時間最多的那一根胡蘿蔔就行
ll a[N], b[N];
ll get(int x, int k) {
if (a[x] < k) return 1e9;
ll siz = a[x] / k, res = a[x] % k;
return sqr(siz + 1) * res + sqr(siz) * (k - res);
}
int main() {
IOS; cin >> n >> k;
priority_queue<PLI> q;
rep (i, 1, n) {
cin >> a[i], b[i] = 1;
q.push({ get(i, 1) - get(i, 2), i });
}
rep (i, n + 1, k) {
int x = q.top().se; q.pop();
++b[x]; q.push({ get(x, b[x]) - get(x, b[x] + 1), x });
}
ll ans = 0;
rep (i, 1, n) ans += get(i, b[i]);
cout << ans;
return 0;
}
F
//下標從1開始
dp(線段樹也可), 常數大的O(n)
g[i] 表示 j~i (1 <= j <= i) 對答案的貢獻, 那麼最後的答案就是 \(\sum g[i]\)
明顯 s[i] == '0', g[i] == g[i - 1],
對於 s[i] == '1', 討論要複雜一些
因為是考慮區間最長的 '1', 理所應當我們要存個 t[i], 表示以 i 結尾的最長 '1', 畢竟在遍歷 i 的時候, 區間最長 '1' 是會變的
顯然 if s[i] == '0', t[i] = 0; else t[i] = t[i-1] + 1
當從 i - 1 到 i 的時候最後一段 '1' 變長了(s[i] == '1') 也就是說, 在上一段最後 t[i]個字元 到 (i - 1) - t[i - 1] 原本最長段是 t[i - 1](t[i - 1] == t[i] - 1)
然而現在成了 t[i], 也就說在這個區間內 多貢獻了個區間長度, 對於 i-t[i]~i 也是多貢獻了個區間長度
設 f[i] 表示 上一個長度為 i 的 '1' 串的起始位置, "1101" 在對於第4個位置的時候, 當前 '1' 段為 [4,4], 那麼上一個 長度為 1 的段 是 [2, 2], 長度為2的段時[1, 2], 故 f[1] = 2, f[2] = 2
那麼 s[i] == '1' 轉移就已經出來了 g[i] = g[i + 1] + (i - t[i] - f[t[i]]) + t[i];
那麼現在的問題成了怎麼求 f[i], 顯然 f[i] 對於 '1' 段是倒著的, "111110", 對於6位置 f[1] = 5, f[2] = 2, f[3] = 3, f[4] = 4, f[5] = 5;
其實已經發現了 f[i] = 上一個串的長度 - 正著數的位置, 比如對於 位置 i,
f[length(包含i的最長 '1' 的長度) - t[i] + 1] = i
至於長度嘛, 預處理O(n), 就行了, 雖然我用的並查集, 但還是O(1)的合併, 直接和 '1' 段的起始座標最為祖先
然後我們又發現個問題, f[i] 隨著 i 更新, 但我們計算 g[i] 的時候是想要未更新之前的 f[i], 如果先轉移完, 在取跟新 f[i], 那必定超時(每遇到一個 '1' 串, 你都要重新遍歷一遍)
所以每個位置存兩個 f[i] 就好了, 當前的和上一個的, 通過下標判斷, 應該取哪個和更新哪個
這樣 f[i] 我們也有了
總的複雜度 O(n), 常數可能有點大, 並查集這裡直接是O(1)
#include <bits/stdc++.h>
#define all(n) (n).begin(), (n).end()
#define se second
#define fi first
#define pb push_back
#define mp make_pair
#define sqr(n) (n)*(n)
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i=(a);i>=(b);--i)
#define IOS ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr)
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> PII;
typedef pair<ll, ll> PLL;
typedef pair<ll, int> PLI;
typedef vector<int> VI;
typedef double db;
template<class T1, class T2> bool umin(T1& a, T2 b) { return a > b ? (a = b, true) : false; }
template<class T1, class T2> bool umax(T1& a, T2 b) { return a < b ? (a = b, true) : false; }
template<class T> void clear(T& a) { T().swap(a); }
const int N = 5e5 + 5;
int n, m, _, k;
int fa[N], siz[N];
ll g[N], t[N];
PII f[N];
char s[N];
int find(int x) {
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
void unit(int x, int y) {
x = find(x); fa[y] = x; siz[x] += siz[y];
}
int get(int x, int k) {
if (f[k] == PII{ 0, 0 }) return 0;
if (f[k].se > f[k].fi) swap(f[k].fi, f[k].se);
if (fa[f[k].fi] != fa[x]) return f[k].fi;
return f[k].se;
}
void change(int x, int k) {
if (f[k].fi == 0) f[k].fi = x;
else if (f[k].se == 0) f[k].se = x;
else if (f[k].fi > f[k].se) f[k].se = x;
else f[k].fi = x;
}
int main() {
IOS; cin >> n >> s + 1;
rep(i, 1, n) fa[i] = i;
rep(i, 1, n)
if (s[i] == '1') {
t[i] = t[i - 1] + 1; siz[i] = 1;
if (s[i - 1] == '1') unit(i - 1, i);
}
rep(i, 1, n) {
if (s[i] == '0') g[i] = g[i - 1];
else {
g[i] = g[i - 1] + i - t[i] - get(i, t[i]) + t[i];
int a = siz[find(i)]; change(i, a - t[i] + 1);
}
}
ll ans = 0;
rep(i, 1, n) ans += g[i];
cout << ans;
return 0;
}