ABC 369

Bubble_e發表於2024-09-01

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\) 個標記點

解答

如果格子小一點, 那麼就是個普及組題。既然這樣, 那麼我們的演算法肯定是基於標記點個數而非整個網格的。

考慮 ”傳統“ 的轉移:

\[dp(i, j) = max\{dp(ii, jj)\} + 1 \]

\[(ii, jj) 在 (i, j) 的左上方 \]

所以想起了二維問題的常見處理思路:固定一維掃另一維

從上到下掃過 \(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. 查詢到 1 的最大距離
  2. 點到根的路徑上邊權置零

性質: 路徑都是向根且連續的!並且每條邊只會修改一次

求一下 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);
	}
}