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;
}