20240309

contiguous發表於2024-03-09

瑞士輪

思路:

快排會g,所以要歸併排序

define int long long會g,關掉

快排函式: stable_sort,用法和sort一樣

#include <bits/stdc++.h>
using namespace std;

// #define int long long
struct inf {
    int score;
    int id;
    int force;
};

bool cmp(inf a, inf b) {
    if(a.score == b.score) {
        return a.id < b.id;
    }
    return a.score > b.score;
}

void solve() {
    int N, R, Q;
    cin >> N >> R >> Q;

    N *= 2;
    vector<inf> v(N);
    for (int i = 0; i < N; i++) {
        v[i].id = i + 1;
        cin >> v[i].score;
    }
    for (int i = 0; i < N; i++) {
        cin >> v[i].force;
    }

    
    stable_sort(v.begin(), v.end(), cmp);
    
    for (int i = 0; i < R; i++) {
        for (int j = 0; j < N; j += 2) {
            if(v[j].force > v[j + 1].force) {
                v[j].score++;
            }
            else {
                v[j + 1].score++;
            }
        }
        stable_sort(v.begin(), v.end(), cmp);
    }
    
    cout << v[Q - 1].id << endl;
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    int T = 1;

    while(T--) {
        solve();
    }

    return 0;
}

其實這是靠歸排卡過去的,正解如下:

在第一次排完序之後的第一場比賽結束後,贏者的順序和敗者的順序相對不變,就是兩個集合,因為贏都是加一分,所以我們考慮把兩個順序不變的陣列合並起來:歸併!

#include <bits/stdc++.h>
using namespace std;

// #define int long long
struct inf {
    int score;
    int id;
    int force;
};

bool cmp(inf a, inf b) {
    if(a.score == b.score) {
        return a.id < b.id;
    }
    return a.score > b.score;
}

void solve() {
    int N, R, Q;
    cin >> N >> R >> Q;

    N *= 2;
    vector<inf> v(N);
    for (int i = 0; i < N; i++) {
        v[i].id = i + 1;
        cin >> v[i].score;
    }
    for (int i = 0; i < N; i++) {
        cin >> v[i].force;
    }

    
    stable_sort(v.begin(), v.end(), cmp);
    
    for (int i = 0; i < R; i++) {
        for (int j = 0; j < N; j += 2) {
            if(v[j].force > v[j + 1].force) {
                v[j].score++;
            }
            else {
                v[j + 1].score++;
            }
        }
        stable_sort(v.begin(), v.end(), cmp);
    }
    
    cout << v[Q - 1].id << endl;
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    int T = 1;

    while(T--) {
        solve();
    }

    return 0;
}

B - 傳送門

思路:

首先開兩個邊值陣列

四重迴圈,列舉邊權為0的邊需要兩重迴圈[i, j]

然後再列舉兩個點[k, g]看是否更新邊權,看[k, i] + [j, g] 和[k, j] + [i, g],這兩條邊是否能更新[k,g]

#include <bits/stdc++.h>
using namespace std;

#define int long long
int e[105][105];
int ee[105][105];

void solve() {
    int n, m;
    cin >> n >> m;

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            if(i != j) {
                e[i][j] = 1e18;
            }
            else {
                e[i][j] = 0;
            }
        }
    }

    for (int i = 0; i < m; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        e[u][v] = e[v][u] = w;
    }

    for (int k = 1; k <= n; k++) {
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                if(e[i][k] != 1e18 && e[k][j] != 1e18) {
                    e[i][j] = min(e[i][j], e[i][k] + e[k][j]);
                }            
            }
        }
    }

    int ans = 1e18;

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {

            for (int k = 1; k <= n; k++) {
                for (int g = 1; g <= n; g++) {
                    ee[k][g] = e[k][g];
                }
            }
            ee[i][j] = ee[j][i] = 0;

            for (int k = 1; k <= n; k++) {
                for (int g = 1; g <= n; g++) {
                    ee[k][g] = min(ee[k][g], min(ee[k][i] + ee[j][g], ee[k][j] + ee[i][g]));
                }
            }
            int res = 0;
            for (int k = 1; k <= n; k++) {
                for (int g = k + 1; g <= n; g++) {
                    res += ee[k][g];
                }
            }
            ans = min(ans, res);
        }
    }

    cout << ans << endl;
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    int T = 1;

    while(T--) {
        solve();
    }

    return 0;
}

D - 菜餚製作

思路:

因為題目有邊的限制條件(正常拓撲)然後要輸出序號的順序儘可能小的先輸出

為什麼要反向拓撲?

我理解的是,正向拓撲優先佇列小的先輸出不行,因為題目要求優先輸出小的,但是小的可能藏在大的後面,所以正向不能貪心

但是反向貪心是正確的:因為我們保證反向先輸出大的,而反向的起點正是正向的終點,也就是輸出的結果,所以反向拓撲可以貪心保證正確性

#include <bits/stdc++.h>
using namespace std;

// #define int long long
vector<int> adj[100005];

void solve() {
    int n, m;
    cin >> n >> m;

    vector<int> in(n + 1, 0);
    for (int i = 1; i <= n; i++) {
        adj[i].clear();
    }

    for (int i = 0; i < m; i++) {
        int u, v;
        cin >> u >> v;
        adj[v].push_back(u);
        in[u]++;
    }

    priority_queue<int> q;
    for (int i = 1; i <= n; i++) {
        if(in[i] == 0) {
            q.push(i);
        }
    }

    vector <int> ans;
    while(q.size()) {
        int x = q.top();
        ans.push_back(x);
        q.pop();

        for (auto it : adj[x]) {
            if(--in[it] == 0) {
                q.push(it);
            }
        }
    }

    if(ans.size() == n) {
        for (int i = n - 1; i >= 0; i--) {
            cout << ans[i] << " ";
        }
        cout << endl;
    }
    else {
        cout << "Impossible!\n";
    }
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    int T = 1;
    cin >> T;

    while(T--) {
        solve();
    }

    return 0;
}

E - 資料備份

題意:

n個點,n個點距離起點的距離已知,要求連k條邊,2k個邊的頂點相異,求這k跳變的最小長度和

思路:

題目範圍是 $N[1, 10 ^ 5]$ 時間是500ms,要求演算法是O(nlogn)級別的

這個題是個反悔貪心,核心機制是選一個點,然後這個點的權值設定成 (l + r) - x,如果再選這個就相當於選了兩邊的然後扔掉中間的,妙

#include <bits/stdc++.h>
using namespace std;

#define int long long

struct node {
    int l, r;
    int id;
    int val;
};

struct inf {
    int val;
    int id;

    bool operator< (const inf & a) const {
        return val > a.val;
    }
};

void solve() {
    int n, m;
    cin >> n >> m;

    vector<int> vv(n);
    for (int i = 0; i < n; i++) {
        cin >> vv[i];
    }

    vector<int> v;
    v.push_back(0);

    for (int i = 1; i < n; i++) {
        v.push_back(vv[i] - vv[i - 1]);
    }

    priority_queue<inf> q;
    for (int i = 1; i < n; i++) {
        q.push({v[i], i});
    }
    // 1 - 4

    vector<int> vis(n + 1, 0);
    vector<node> lst(n + 1);

    lst[0].val = lst[n].val = (int)(9e18);
    for (int i = 1; i < n ; i++) {
        lst[i].l = i - 1;
        lst[i].r = i + 1;
        
        lst[i].id = i;
        lst[i].val = v[i];
    }   

    int ans = 0;
    int cnt = 0;
    while(cnt < m) {
        auto it = q.top();
        q.pop();
        // cerr << it.id << " " << it.val << endl;
        if(vis[it.id] == 1) {
            continue;
        }
        ans += it.val;
        int _l = lst[it.id].l;
        int _r = lst[it.id].r;
        vis[_l] = 1, vis[_r] = 1;

        lst[it.id].val = lst[_l].val + lst[_r].val - lst[it.id].val;
        q.push({lst[it.id].val, it.id});

        lst[it.id].l = lst[_l].l;
        lst[it.id].r = lst[_r].r;
        lst[lst[_l].l].r = it.id;
        lst[lst[_r].r].l = it.id;

        cnt++;
    }
    cout << ans << endl;
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    int T = 1;
    // cin >> T;

    while(T--) {
        solve();
    }

    return 0;
}

反悔貪心

  • 前兩道題都是利用堆的特性,維護一個利益最大值,每次更新失敗的時候:把元素壓入堆,把堆頂元素去除。

  • 反悔貪心允許我們在發現現在不是全域性最優解的情況下,回退一步或者若干步,去採取另外的策略得到全域性最優解。

P2949 [USACO09OPEN] Work Scheduling G

題意:

n件事,每件事有截止日期和收益,有1e9的時間(無限長),但是每一秒只能做一件事,一件事只需要一秒。

問能獲得的最大收益?

思路:

起初就是按照時間第一關鍵字,收益第二關鍵字排序,然後只要時間夠就加,貪心只有40分

我們思考一下,如果在後期有收益比前期高的事件,可以放棄做前面那個,優先做後面那個

實現:按第一關鍵字排序之後,小根堆維護事件收益。

#include <bits/stdc++.h>
using namespace std;

#define int long long

void solve() {
	int n;
	cin >> n;

	vector<pair<int, int>> v(n);
	for (int i = 0; i < n; i++) {
		cin >> v[i].first >> v[i].second;
	}

	sort(v.begin(), v.end());

	priority_queue<int, vector<int>, greater<int>>q;

	int day = 0;
	int res = 0;
	for (auto [t, w] : v) {
		if(day + 1 <= t) {
			res += w;
			q.push(w);
			day++;
		}
		else {
			if(q.top() < w) {
				res -= q.top();
				q.pop();
				q.push(w);
				res += w;
			}
		}
	}

	cout << res << endl;
 
}

signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);

	int T = 1;
	// cin >> T;

	while(T--) {
		solve();
	}

	return 0;
}

P4053 [JSOI2007] 建築搶修

題意:

n個建築需要修復,對於每個建築,給兩個引數$t1, t2$,表示修築需要的時間和修築的截止時間。

問能最多能修復多少建築。

思路:

這個題的特點就是選擇一種策略,來找最多的東西,看著像揹包dp,其實是個反悔貪心。

先搞清楚怎麼才能讓建築儘可能多?那就是建築所需要的時間儘可能少。

利用大根堆維護修復建築所需要的時間。

我們先根據t2排序,然後迴圈遍歷

  • 如果當前的時間足以修復這個建築,那就修
  • 否則,就說明之前的時間已經過了修復這個建築的時間,我們要把這個建築壓入大根堆,然後把最大的建築時間剔除,這就是反悔
#include <bits/stdc++.h>
using namespace std;

#define int long long

struct  inf{
	int t1, t2;
};

bool cmp (inf a, inf b) {
	return a.t2 < b.t2;
}

void solve() {
	int n;
	cin >> n;

	vector<inf> v(n);
	for (int i = 0; i < n; i++) {
		cin >> v[i].t1 >> v[i].t2;
	}

	sort(v.begin(), v.end(), cmp);

	priority_queue<int> q;
	int sum = 0;
	int ans = 0;
	for (auto [t1, t2] : v) {
		q.push(t1);
		sum += t1;
		if(sum <= t2) {
			ans++;
		}
		else {
			sum -= q.top();
			q.pop();
		}
	}

	cout << ans << endl;
}

signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);

	int T = 1;
    
	while(T--) {
		solve();
	}

	return 0;
}

P2107 小Z的AK計劃

題意:

n個點,m的時間。

每個點有位置和停留時間,問最多解決的點數。

思路:

這個題我學會了,反悔貪心就是先做,後反悔,

就像這個題,我們直接按照位置順序排序,

然後對時間用大根堆維護,每次取優。

直到time < 0 我們開始出隊,如果出到time < 0,就break

#include <bits/stdc++.h>
using namespace std;

#define int long long

struct inf {
	int x, t;
};

bool cmp (inf a, inf b) {
	return a.x < b.x;
}

void solve() {
	int n, m;
	cin >> n >> m;

	vector<inf> v(n);
	for (int i = 0; i < n; i++) {
		cin >> v[i].x >> v[i].t;
	}

	int time = 10;
	int loc = 0;
	int ans = 0;
	int res = 0;
	sort(v.begin(), v.end(), cmp);

	priority_queue<int> q;
	for (auto [x, t] : v) {
		if(time - x + loc - t >= 0) {
			time -= t + x - loc;
			q.push(t);
			ans++;
			loc = x;
		}
		else {
			while(q.size()) {
				time += q.top();
				q.pop();
				ans--;
				if(time - x + loc - t >= 0) {
					break;
				}
			}
			if(time - x + loc - t >= 0) {
				q.push(t);
				time -= x + t;
			}
			else 
		}
		res = max(res, ans);
	}

 	cout << res << endl;
}

signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);

	int T = 1;
	// cin >> T;

	while(T--) {
		solve();
	}

	return 0;
}

Buy Low Sell High

題意:

n天,

上午進$a_i$件,下午有一個人買$b_i$ 件,問最多能滿足多少人的需求

思路:

貪心直接做會出現大客戶的情況,所以要反悔,能賣就賣,開一個大根堆,維護大客戶

最後輸出

#include <bits/stdc++.h>
using namespace std;

#define int long long

struct inf {
	int a, b;
};

struct inf2 {
	int t;
	int id;

	bool operator<(const inf2& a) const{
		return t < a.t;
	}
};

void solve() {
	int n;
	cin >> n;

	vector<inf> v(n);
	for (int i = 0; i < n; i++) {
		cin >> v[i].a;
	}
	for (int i = 0; i < n; i++) {
		cin >> v[i].b;
	}

	int cnt = 0;
	int ans = 0;

	priority_queue<inf2> q;
	for (int i = 0; i < n; i++) {

		int a = v[i].a, b = v[i].b;
		cnt += a;

		q.push({b, i + 1});
		cnt -= b;
		if(cnt >= 0) {
			ans++;
		}
		else {
			cnt += q.top().t;
			q.pop();
		}
	}

	cout << ans << endl;
	vector<int> vt;

	while(q.size()) {
		vt.push_back(q.top().id);
		q.pop();
	}

	sort(vt.begin(), vt.end());
	for (auto it : vt) {
		cout << it << " ";
	}
	cout << endl;
}

signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);

	int T = 1;
	// cin >> T;

	while(T--) {
		solve();
	}

	return 0;
}