ABC 369
剛才翻上次寫的 abc 366 題解, 發現語言挺抽象, 導致自己都快看不懂了, 這回寫好點
這段時間第一次 Rated, 情況一般吧, F 忘給同一個 \(x\) 的所有 \(y\) 排序了, 今天 (9.1) 早上突然看出來了。G 沒有細看, 以為是個博弈論, 現在才發現是個簡單貪心
369 這數挺吉利哈哈, 濟南好像有說法是初三初六初九宜出行, 濟南公交也有個 app 叫 369。這次就當作重新學 oi 的開始了, 369, 宜踏上新的征程!
A
題意
給定 \(a, b\), 求一個 \(x\), 使得三個數滿足 \(q - p = r - q\) 的形式(\(a, b, x\) 順序可變, 同一個 \((q, p, r)\) 只算一次)
解答
直接分類討論即可, 重點是 \(a, b, x\) 存在至少兩個數相等時怎麼辦
更簡單的寫法是直接列舉 \(x\), 範圍 \([-200, 200]\) , map 判一下重的 \((q, p, r)\) 即可
程式碼
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 10, MOD = 998244353;
int a, b;
int ans;
signed main(){
// freopen("1.in", "r", stdin);
cin >> a >> b;
++ans;
if((a + b) / 2 != a && (a + b) / 2 != b && (a + b) % 2 == 0) ++ans;
if(a != b) ++ans;
cout << ans << "\n";
}
B
題意
彈鋼琴, 左右手相互獨立, 按順序給出每一步左/右手需要到達的位置座標。
定義疲勞度為兩座標的差的絕對值, 手的起始位置由你決定, 求最小疲勞度
解答
這題有點詐騙啊
首先每步操作按順序給出, 不能改變
然後起始位置設成左/右手分別的第一步操作的座標即可, 剩下的是語法組內容
程式碼
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 10, MOD = 998244353;
int n, v;
char opt;
vector <int> l, r;
signed main(){
// freopen("1.in", "r", stdin);
cin >> n;
for(int i = 1; i <= n; i++){
cin >> v >> opt;
if(opt == 'L') l.push_back(v);
else r.push_back(v);
}
// sort(l.begin(), l.end());
// sort(r.begin(), r.end());
int ans = 0;
for(int i = 1; i < l.size(); i++){
ans += abs(l[i] - l[i - 1]);
}
for(int i = 1; i < r.size(); i++){
ans += abs(r[i] - r[i - 1]);
}
cout << ans << "\n";
}
C
題意
給定正整數序列, 求有序不可重數對 \((l, r)\) 的個數, 使得 \(a_l...a_r\) 是等差數列(\(l, r\) 可以相等)
解答
判斷等差數列常見套路:差分相等
然後就沒了
程式碼
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 10, MOD = 998244353;
int n;
int a[N], cha[N];
signed main(){
// freopen("1.in", "r", stdin);
cin >> n;
for(int i = 1; i <= n; i++){
cin >> a[i];
cha[i] = a[i] - a[i - 1];
}
int lst = cha[2], cnt = 1, ans = n;
for(int i = 3; i <= n; i++){
if(cha[i] == lst){
++cnt;
}else{
ans += (1 + cnt) * cnt / 2;
cnt = 1;
lst = cha[i];
}
}
if(n != 1) ans += (1 + cnt) * cnt / 2;
cout << ans << "\n";
}
D
題意
打怪, 打死每隻怪物可以獲得一定的經驗, 必須按順序, 但可以跳過一些怪物
對於你打死的第 \(i\) 只怪物, 如果 \(i\) 是偶數, 可以獲得雙倍經驗
解答
小 DP
設 \(odd(i)\) 表示考慮到第 \(i\) 只怪物, 一共打死了奇數只, 獲得的最大經驗, \(even(i)\) 同理
每次轉移是 \(O(1)\) 的
odd[i] = max(odd[i - 1], even[i - 1] + a[i]);
even[i] = max(even[i - 1], odd[i - 1] + a[i] * 2);
但是邊界要稍微處理一下, 我因此 Wa 了一發
程式碼
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 10, INF = 1e18;
int n;
int a[N], odd[N], even[N];
signed main(){
// freopen("1.in", "r", stdin);
cin >> n;
for(int i = 1; i <= n; i++){
cin >> a[i];
}
odd[0] = -INF, even[0] = 0;
for(int i = 1; i <= n; i++){
odd[i] = max(odd[i - 1], even[i - 1] + a[i]);
even[i] = max(even[i - 1], odd[i - 1] + a[i] * 2);
}
int ans = max(odd[n], even[n]);
cout << ans << "\n";
}
E
題意
有一些節點和雙向道路。
你的目標是從 1 走到 n, 每次指定 \(O(1)\) 條道路, 完成上述目標的同時必須經過這些道路至少一次, 求最短距離
解答
只限定了 \(5\) 條道路, \(3e3\) 次詢問, 很難不想著鑽空子
不妨列舉經過這些道路的順序, 共 \(5!\) 種, 再列舉每條路從 \(u\) 到 \(v\) 還是反過來, \(2^5\) 種, 發現完全可過
然後就完事了, 最短路 Floyd 都可以做, 程式碼複雜度極低哈哈哈
程式碼
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 4e2 + 10, M = 2e5 + 10, INF = 1e18;
int n, m;
int u, v, w;
int e[N][N], mini;
int q, k, b[N];
struct Edge{
int u, v, w;
}rec[M];
void Floyd(){
for(int k = 1; k <= n; k++){
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
e[i][j] = min(e[i][j], e[i][k] + e[k][j]);
}
}
}
}
void Dfs(int x, int st, int dis){
if(x == k + 1){
mini = min(mini, dis + e[st][n]);
return;
}
Dfs(x + 1, rec[b[x]].v, dis + e[st][rec[b[x]].u] + rec[b[x]].w);
Dfs(x + 1, rec[b[x]].u, dis + e[st][rec[b[x]].v] + rec[b[x]].w);
}
signed main(){
// freopen("1.in", "r", stdin);
cin >> n >> m;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
e[i][j] = INF;
}
e[i][i] = 0;
}
for(int i = 1; i <= m; i++){
cin >> u >> v >> w;
rec[i] = {u, v, w};
e[u][v] = min(e[u][v], w);
e[v][u] = e[u][v];
}
Floyd();
cin >> q;
for(int i = 1; i <= q; i++){
cin >> k;
for(int j = 1; j <= k; j++){
cin >> b[j];
}
mini = INF;
do{
Dfs(1, 1, 0);
}while(next_permutation(b + 1, b + 1 + k));
cout << mini << "\n";
}
}
F
題意
網格圖上行走, 只能往右或往下, 其中一些格點打了標記。
問你從 \((1, 1)\) 走到 \((h, w)\) 的過程中最多經過多少標記點, 並且用 \(D, R\) 的方式輸出一種行走方案
網格圖的大小是 \(2e5 * 2e5\) 的, 總共 \(2e5\) 個標記點
解答
如果格子小一點, 那麼就是個普及組題。既然這樣, 那麼我們的演算法肯定是基於標記點個數而非整個網格的。
考慮 ”傳統“ 的轉移:
所以想起了二維問題的常見處理思路:固定一維掃另一維
從上到下掃過 \(x\) 軸, 列舉當前 \(x\) 的對應的所有 \(y\) (這裡一定要排序!!!, 我因此 Wa 了兩發)。
對於確定的 \(y\) , 求出 \(y' \le y\) 的 \(y'\) 對應的最大 \(dp\) 值, 因為固定了 \(x\) 的順序是從上到下, 所以每一個 \(y'\) 記憶體儲的資訊都來自於小於當前 \(x\) 的 \(x'\), 開個線段樹維護一下即可
程式碼
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e7 + 10, INF = 1e18;
int h, w, n;
int r[N], c[N], ir[N], ic[N], cnt;
map <int, int> rec, rcr, rcc;
int rr[N], cc[N], cntr, cntc;
int pre[N];
vector <int> ans;
vector <pair <int, int>> vec[N];
struct Segment_Tree{
struct Node{
int l, r;
int maxi, id;
}t[N];
void Build(int id, int l, int r){
t[id] = {l, r, 0};
if(l == r) return;
int mid = (l + r) / 2;
Build(id * 2, l, mid);
Build(id * 2 + 1, mid + 1, r);
}
void Modify(int id, int l, int r, int x, int idx){
if(r < t[id].l || t[id].r < l) return;
if(l <= t[id].l && t[id].r <= r){
// t[id].maxi = max(t[id].maxi, x);
if(x > t[id].maxi){
t[id].maxi = x;
t[id].id = idx;
}
return;
}
Modify(id * 2, l, r, x, idx), Modify(id * 2 + 1, l, r, x, idx);
if(t[id * 2].maxi > t[id * 2 + 1].maxi){
t[id].maxi = t[id * 2].maxi;
t[id].id = t[id * 2].id;
}else{
t[id].maxi = t[id * 2 + 1].maxi;
t[id].id = t[id * 2 + 1].id;
}
}
pair <int, int> Query(int id, int l, int r){
if(r < t[id].l || t[id].r < l) return {0, 0};
if(l <= t[id].l && t[id].r <= r){
return {t[id].maxi, t[id].id};
}
auto a = Query(id * 2, l, r);
auto b = Query(id * 2 + 1, l, r);
if(b.first > a.first){
a = b;
}
return a;
}
}t;
signed main(){
// freopen("1.in", "r", stdin);
// freopen("1.out", "w", stdout);
cin >> h >> w >> n;
for(int i = 1; i <= n; i++){
cin >> r[i] >> c[i];
rr[++cntr] = r[i];
cc[++cntc] = c[i];
}
sort(rr + 1, rr + 1 + cntr);
sort(cc + 1, cc + 1 + cntc);
int ca = 0;
for(int i = 1; i <= cntr; i++){
if(!rcr[rr[i]]) rcr[rr[i]] = ++ca;
}
int cb = 0;
for(int i = 1; i <= cntc; i++){
if(!rcc[cc[i]]) rcc[cc[i]] = ++cb;
}
for(int i = 1; i <= n; i++){
vec[rcr[r[i]]].push_back({rcc[c[i]], i});
}
t.Build(1, 1, cb);
for(int i = 1; i <= ca; i++){
sort(vec[i].begin(), vec[i].end());
for(auto j : vec[i]){
auto v = t.Query(1, 1, j.first);
pre[j.second] = v.second;
t.Modify(1, j.first, j.first, v.first + 1, j.second);
}
}
// cout << t.Query(1, 1, cb) << "\n"
int anss = t.Query(1, 1, cb).first;
int ret = t.Query(1, 1, cb).second;
r[n + 10] = h, c[n + 10] = w;
ans.push_back(n + 10);
while(ret){
ans.push_back(ret);
ret = pre[ret];
}
r[n + 20] = 1, c[n + 20] = 1;
ans.push_back(n + 20);
reverse(ans.begin(), ans.end());
// for(auto i : ans) cout << i << " ";
// cout << "\n";
cout << anss << "\n";
for(int i = 1; i < ans.size(); i++){
int id = ans[i], lst = ans[i - 1];
int x = abs(r[id] - r[lst]), y = abs(c[id] - c[lst]);
// cout << x << "+" << y << "\n";
for(int j = 1; j <= x; j++) cout << "D";
for(int j = 1; j <= y; j++) cout << "R";
}
cout << "\n";
}
G
題意
倆人在玩遊戲, 物件是一棵樹, 根為 1
其中一個人 A 在樹上指定了 \(k\) 個節點, 另一個人 B 要從 1 出發, 經過這些點回到 1
A 想最大化路徑長度, B 想最小化, 求對於 \(k = 1, 2, ..., n\) , 最優策略下的路徑長度
解答
披著博弈外皮的貪心
如果告訴你這 \(k\) 個點, 那麼 \(B\) 的最優策略就是這 \(k\) 個點到根的路徑並的長度 \(*2\) , 這個顯然, 或者手算一下就可以得出
如果這 \(k\) 個點是依次加入集合的, 那麼每次的增量就是加入點到根的路徑上、之前沒被走過的邊的邊權和 (\(*2\))
對於 A, 他的策略就是選一個當前情況(有一些邊被走過, 不重複計入)下距離 1 最遠的點, 加入集合
所以只需要實現一個資料結構, 支援:
- 查詢到 1 的最大距離
- 點到根的路徑上邊權置零
性質: 路徑都是向根且連續的!並且每條邊只會修改一次
求一下 Dfn 然後線段樹維護即可
程式碼
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6 + 10, MOD = 998244353;
int n;
int u, v, w;
int dfn[N], cdfn, fa[N][2], dis[N], si[N]; // fa : father, edge_val
int tag[N];
struct Edge{
int v, w;
};
vector <Edge> e[N];
struct Segment_Tree{
struct Node{
int l, r;
int maxi, maxid, tag;
}t[N];
struct Ret{
int maxi, maxid;
}ret;
void Update(int id, int x){
t[id].maxi += x;
t[id].tag += x;
}
void Push_down(int id){
Update(id * 2, t[id].tag);
Update(id * 2 + 1, t[id].tag);
t[id].tag = 0;
}
void Push_up(int id){
if(t[id * 2].maxi > t[id * 2 + 1].maxi){
t[id].maxi = t[id * 2].maxi;
t[id].maxid = t[id * 2].maxid;
}else{
t[id].maxi = t[id * 2 + 1].maxi;
t[id].maxid = t[id * 2 + 1].maxid;
}
}
void Build(int id, int l, int r){
t[id].l = l, t[id].r = r;
if(l == r){
t[id].maxi = dis[l];
t[id].maxid = l;
return;
}
int mid = (l + r) / 2;
Build(id * 2, l, mid);
Build(id * 2 + 1, mid + 1, r);
Push_up(id);
}
void Modify(int id, int l, int r, int x){
if(r < t[id].l || t[id].r < l){
return;
}
if(l <= t[id].l && t[id].r <= r){
Update(id, x);
return;
}
Push_down(id);
Modify(id * 2, l, r, x);
Modify(id * 2 + 1, l, r, x);
Push_up(id);
}
Ret Query(int id, int l, int r){
if(r < t[id].l || t[id].r < l){
return {0, 0};
}
if(l <= t[id].l && t[id].r <= r){
return {t[id].maxi, t[id].maxid};
}
Push_down(id);
Ret a = Query(id * 2, l, r);
Ret b = Query(id * 2 + 1, l, r);
a = (a.maxi > b.maxi) ? a : b;
return a;
}
}tr;
void Get_Dfn(int x, int y, int w){
dfn[x] = ++cdfn;
si[dfn[x]] = 1;
dis[dfn[x]] = dis[dfn[y]] + w;
fa[dfn[x]][0] = dfn[y], fa[dfn[x]][1] = w;
for(auto i : e[x]){
if(i.v == y) continue;
Get_Dfn(i.v, x, i.w);
si[dfn[x]] += si[dfn[i.v]];
}
}
struct Delete{
int idx, val;
};
vector <Delete> del;
void Del(int x){
int p = x;
while(!tag[p]){
tag[p] = 1;
tr.Modify(1, p, p + si[p] - 1, -fa[p][1]);
p = fa[p][0];
}
}
signed main(){
// freopen("1.in", "r", stdin);
cin >> n;
for(int i = 1; i < n; i++){
cin >> u >> v >> w;
e[u].push_back({v, w});
e[v].push_back({u, w});
}
Get_Dfn(1, 0, 0);
tr.Build(1, 1, n);
tag[1] = 1; // 終止點
int ans = 0;
for(int i = 1; i <= n; i++){
auto ret = tr.Query(1, 1, n);
// cout << ret.maxid << " " << ret.maxi << "\n";
ans += ret.maxi * 2;
cout << ans << "\n";
Del(ret.maxid);
}
}