24-3-5 個人賽

mostimali發表於2024-03-05




A - 瑞士輪

難度: ⭐⭐⭐

題目大意

現有n個人, n一定是偶數, 每個人都有一個初始分數p和能力值s; 進行進行r輪比賽, 每輪比賽先按分數將n人進行排序, 第一名和第二名比, 第三名和第四名比, 以此類推, 能力值高者獲勝, 勝者加一分, 敗者不加分; 問r輪過後排名第k的人是誰;

解題思路

本題只給了500ms, 所以不能每輪都sort一遍, 因為sort是插入排序; 本題需要用歸併排序, 因為歸併排序對於一個有序的序列是不用遞迴的, 也就是O(n)的複雜度;

神秘程式碼

#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define endl '\n'
using namespace std;
const int N = 3e5 + 10, mod = 998244353, inf = 1e18;
typedef pair<int, int> PII;
int n, m, k;
int p[N], s[N], f[N];
PII win[N], lose[N];
bool cmp(int a, int b){
    if(p[a] == p[b]) return a < b;
    return p[a] > p[b];
}
void merge(int l, int r){
    int a = 1, b = 1, c = 0;
    while(a <= l && b <= r){
        if(win[a].second > lose[b].second){
            f[++c] = win[a++].first;
        }
        else if(win[a].second < lose[b].second){
            f[++c] = lose[b++].first;
        }
        else{
            if(win[a].first < lose[b].first) f[++c] = win[a++].first;
            else f[++c] = lose[b++].first;
        }
    }
    while(a <= l) f[++c] = win[a++].first;
    while(b <= r) f[++c] = lose[b++].first;
}
signed main(){
    IOS;
    cin >> n >> m >> k;
    n *= 2;
    for(int i = 1; i <= n; i++) f[i] = i;
    for(int i = 1; i <= n; i++) cin >> p[i];
    for(int i = 1; i <= n; i++) cin >> s[i];
    sort(f + 1, f + 1 + n, cmp);
    while(m--){
        int l = 0, r = 0;
        for(int i = 1; i <= n; i += 2){
            if(s[f[i]] > s[f[i + 1]]){
                p[f[i]]++;
                win[++l] = {f[i], p[f[i]]};
                lose[++r] = {f[i + 1], p[f[i + 1]]};
            }
            else{
                p[f[i + 1]]++;
                win[++l] = {f[i + 1], p[f[i + 1]]};
                lose[++r] = {f[i], p[f[i]]};
            }
        }
        merge(l, r);
        
    }
    cout << f[k] << endl;
	return 0;
}




B - 傳送門

難度: ⭐⭐⭐

題目大意

給定一個有n個點m條邊的無向圖; 邊權均為正數; 現在我們可以選擇兩個頂點, 讓其連起一條邊權為0的邊; 請問怎麼挑選可以讓任意兩個頂點的路徑長度和最小;

解題思路

一個多源最短路, 再加上100的資料範圍不難想到要用floyd; 然後模擬是O(n3)的複雜度, 所以要考慮最佳化; 根據floyd的內涵, 三層迴圈k, i, j是把點k作為i到j路徑上的一個點進行求解; 現在我們在a和b之間加一條邊, 那麼我們就可以再把a和b作為路徑上的點去再更新一遍路徑; 所以我們可以先走一遍floyd把初始路徑都預處理除了, 然後遍歷要新增的邊, 每次用預處理好的資料再進行floyd即可, 因此這裡的floyd就不需要再遍歷k了;

神秘程式碼

#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define endl '\n'
using namespace std;
const int N = 2e2 + 10, mod = 998244353, inf = 1e18;
typedef pair<int, int> PII;
int n, m, res = inf;
string s;
int g[N][N], d[N][N];
void floyd(){
    for(int k = 1; k <= n; k++){
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= n; j++){
                g[i][j] = min(g[i][j], g[i][k] + g[k][j]);
            }
        }
    }
}
void query(int u){
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= n; j++){
            d[i][j] = min(d[i][j], d[i][u] + d[u][j]);
        }
    }
}
void copy(){
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= n; j++){
            d[i][j] = g[i][j];
        }
    }
}
void find(){
    int sum = 0;
    for(int i = 1; i <= n; i++){
        for(int j = i + 1; j <= n; j++){
            sum += d[i][j];
        }
    }
    res = min(res, sum);
}
signed main(){
    IOS;
    cin >> n >> m;
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= n; j++){
            if(i != j) g[i][j] = inf;
        }
    }
    for(int i = 1; i <= m; i++){
        int a, b, c;
        cin >> a >> b >> c;
        g[a][b] = g[b][a] = min(g[a][b], c);
    }
    floyd();
    for(int i = 1; i <= n; i++){
        for(int j = i + 1; j <= n; j++){
            copy();
            d[i][j] = d[j][i] = 0;
            query(i);
            query(j);
            find();
        }
    }
    cout << res;
	return 0;
}




C - Gardening Friends

難度: ⭐⭐⭐⭐




D - 菜餚製作

難度: ⭐⭐⭐

題目大意

給定一個n個頂點m條邊的有向圖, 對於邊(a, b)要求在輸出b之前必須要先輸出a, 也就是隻能輸出當前入度為0的點; 但是對於輸出的順序有以下要求: 必須要儘早輸出1, 並且在此之後又要儘早輸出2, 然後是3, 依次類推; 也就是說, 1要儘可能地在2之前輸出, 同理2也要在3之前輸出;

解題思路

這裡再明確一下題意, 比如有邊(5, 1)和(4, 2); 按照題目要求, 我們的輸出順序是5 1 4 2, 雖然5在4之前輸出看似有違題意, 但是注意題目的我加粗的地方, 他的初始要求只是1要儘可能在2之前輸出, 在滿足這一條之後才會有後面的要求, 也就是說在尚未滿足1在2之前輸出時, 4和5之間沒有約束; 本題很難用普通的拓撲排序來解決該問題, 比如我們輸出1後, 當前入度為0的有3, 4, 5; 並且節點2在5的後面, 那麼我們需要去遍歷圖去找2, 複雜度肯定很高; 既然正向走不通, 那麼可以考慮反向;
我們把該圖看作是若干條相互有連線的鏈, 入度為0的是鏈首, 出度為0的鏈尾; 我們從鏈尾開始找, 越早選中的自然越晚輸出; 對於若干個鏈尾, 我們優先挑選其中最大的那麼一定是有益的, 這不像正向走時不一定挑最小的; 所以我們可以建立反圖, 用大根堆進行拓撲排序, 然後將排序反向輸出即是答案;

神秘程式碼

#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define endl '\n'
using namespace std;
const int N = 2e5 + 10, mod = 998244353, inf = 2e18;
typedef pair<int, int> PII;
int n, m, res;
int d[N];
vector<int> v[N];
signed main(){
    int T;
    cin >> T;
    while(T--){
        vector<int> res;
        int idx = 0;
        priority_queue<int> q;
        cin >> n >> m;
        for(int i = 1; i <= n; i++){
            v[i].clear();
            d[i] = 0;
        }
        for(int i = 1; i <= m; i++){
            int a, b;
            cin >> a >> b;
            d[a]++;
            v[b].push_back(a);
        }
        for(int i = 1; i <= n; i++){
            if(!d[i]) q.push(i);
        }
        while(q.size()){
            int t = q.top();
            q.pop();
            idx++;
            res.push_back(t);
            for(int x : v[t]){
                d[x]--;
                if(!d[x]) q.push(x);
            }
        }
        if(idx == n){
            for(int i = n - 1; i >= 0; i--) cout << res[i] << ' ';
            cout << endl;
        }
        else cout << "Impossible!" << endl;
    }

	return 0;
}




E - 資料備份

難度: ⭐⭐⭐⭐

相關文章