078D
https://atcoder.jp/contests/abc078/tasks/arc085_b
問題陳述
我們有一副由 \(N\) 張牌組成的撲克牌。每張牌上都寫著一個整數。從最上面開始的第 \(i\) 張牌上的整數是 \(a _ i\) 。
兩個人 X 和 Y 將用這副撲克牌玩一個遊戲。最初,X 手中有一張寫有 \(Z\) 的牌,Y 手中有一張寫有 \(W\) 的牌。然後,從 X 開始,他們將交替進行以下操作:
- 從牌頂抽若干張牌。然後,丟棄手中的牌,保留最後抽出的牌。這裡至少要抽出一張牌。
當牌組中沒有牌時,遊戲結束。遊戲的得分是兩位玩家手中牌上所寫整數的絕對差值。
X 玩遊戲的目的是使得分最大,而 Y 玩遊戲的目的是使得分最小。遊戲的得分是多少?
思路
其實可以想到就是,X 能拿到的只有倒數第二個或者倒數第一個,因為如果 X 選擇了中間的任何一個,Y 的都可以剩下一些更差迫使 X 丟棄當前選擇的。所以考慮一下選擇哪一個更有就好了。要特判一下就是如果 \(n=1\) 則只能選擇倒數一個。
#include <bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
using i128 = __int128;
#define int i64
using vi = vector<int>;
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int n, z, w;
cin >> n >> z >> w;
vi a(n + 1);
for(int i = 1; i <= n; i ++) cin >> a[i];
if(n == 1){
cout << abs(a[n] - w);
}else {
cout << max(abs(a[n] - a[n-1]), abs(a[n] - w));
}
return 0;
}
091C
貪心
把紅色點從小到大排序,藍色點從大到小排序,然後對於每個紅色點,找到所有滿足的藍色點中 \(y\) 最小的。
#include <bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
#define int i64
using vi = vector<int>;
using pii = pair<int, int>;
const int inf = 1e9;
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int n;
cin >> n;
vector<pii> a(n), b(n);
for (auto &[x, y]: a) cin >> x >> y;
for (auto &[x, y]: b) cin >> x >> y;
sort(a.begin(), a.end(), greater<>());
sort(b.begin(), b.end());
vi vis(n);
for (auto &[ax, ay]: a) {
int p = -1;
for (int j = 0; j < n; j++) {
if (vis[j]) continue;
if (b[j].first <= ax or b[j].second <= ay) continue;
if (p == -1) p = j;
else if (p != -1 and b[j].second < b[p].second) p = j;
}
if (p != -1) vis[p] = 1;
}
cout << accumulate(vis.begin(), vis.end(), 0ll);
return 0;
}
二分圖最大匹配
直接上匈牙利演算法
#include <bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
#define int i64
using vi = vector<int>;
using pii = pair<int, int>;
const int inf = 1e9;
vector<vi> e;
vector<bool> vis;
vi p;
int n;
bool match(int x) {
for (auto y: e[x]) {
if (vis[y]) continue;
vis[y] = 1;
if (p[y] == 0 or match(p[y])) {
p[y] = x;
return true;
}
}
return false;
}
int Hungarian() {
int cnt = 0;
p.resize(n + n + 1);
for (int i = 1; i <= n; i++) {
vis = vector<bool>(n + n + 1);
if (match(i)) cnt++;
}
return cnt;
}
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
cin >> n;
vector<pii> a(n + n + 1);
for (int i = 1; i <= n + n; i++) cin >> a[i].first >> a[i].second;
e.resize(n + n + 1);
for (int i = 1; i <= n; i++)
for (int j = n + 1; j <= n + n; j++)
if (a[i].first < a[j].first and a[i].second < a[j].second)
e[i].push_back(j), e[j].push_back(i);
cout << Hungarian() << "\n";
return 0;
}
067D
兩個人的最優策略都是優先佔領 1 到 n 路徑上的點,因為被佔領的點,其子樹也不能被佔領。最後統計一下每個人最多可以佔領多少個,比較一下即可。
#include <bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
#define int i64
using vi = vector<int>;
using pii = pair<int, int>;
const int inf = 1e9;
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int n;
cin >> n;
vector<vi> e(n + 1);
for (int i = 1, x, y; i < n; i++)
cin >> x >> y, e[x].push_back(y), e[y].push_back(x);
vi dis1(n + 1, inf);
dis1[1] = 0;
queue<int> q;
q.push(1);
while (not q.empty()) {
int x = q.front();
q.pop();
for (auto y: e[x]) {
if (dis1[y] < dis1[x] + 1) continue;
dis1[y] = dis1[x] + 1, q.push(y);
}
}
vi dis2(n + 1, inf);
dis2[n] = 0;
q.push(n);
while (not q.empty()) {
int x = q.front();
q.pop();
for (auto y: e[x]) {
if (dis2[y] < dis2[x] + 1) continue;
dis2[y] = dis2[x] + 1, q.push(y);
}
}
vi color(n + 1);
queue<int> q1, q2;
for (int i = 1; i <= n; i++) {
if (dis1[i] + dis2[i] != dis1[n]) continue;
if (dis1[i] <= dis2[i]) q1.push(i);
else q2.push(i), color[i] = 2;
}
while (not q1.empty()) {
int x = q1.front();
q1.pop();
for (auto y: e[x]) {
if (color[y]) continue;
color[y] = 1, q1.push(y);
}
}
while (not q2.empty()) {
int x = q2.front();
q2.pop();
for (auto y: e[x]) {
if (color[y]) continue;
color[y] = 2, q2.push(y);
}
}
vi cnt(3);
for (int i = 1; i <= n; i++)
cnt[color[i]]++;
if (cnt[1] > cnt[2]) cout << "Fennec";
else cout << "Snuke";
return 0;
}
123D
雖然列舉的種類是 \(10^9\) 但是注意到 \(k\) 最大是 \(3000\),因此可以暴力列舉 \(A,B\) 然後取前 \(K\) 個,然後再暴力列舉即可。
#include <bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
#define int i64
using vi = vector<int>;
const int inf = 1e9;
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int x, y, z, k;
cin >> x >> y >> z>> k;
vi a(x), b(y), c(z);
for (auto &i: a) cin >> i;
for (auto &i: b) cin >> i;
for (auto &i: c) cin >> i;
vi ab;
for (const auto &ai: a)
for (const auto &bi: b)
ab.push_back(ai + bi);
sort(ab.begin(), ab.end() , greater<>());
ab.resize(min((int) ab.size(), k));
vi abc;
for (const auto &abi: ab)
for (const auto &ci: c)
abc.push_back(abi + ci);
sort(abc.begin(), abc.end(), greater<>());
abc.resize(min((int) abc.size(), k));
for(const auto abci : abc) cout << abci << "\n";
return 0;
}
046D
可以想到的是能出布就出布,因為出布不會減分,但是出石頭可能會減分。所以出的順序應該是 gpgpgp
#include <bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
using i128 = __int128;
#define int i64
using vi = vector<int>;
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
string s;
cin >> s;
int res = 0;
for(int i = 1; i < s.size(); i += 2)
res += (s[i] == 'g');
for(int i = 0; i < s.size(); i += 2)
res -= (s[i] == 'p');
cout << res;
return 0;
}
096D
考慮只選擇個位為 \(1\) 的質數,這樣的話五個數的和一定為 \(5\) 的倍數。
#include <bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
using i128 = __int128;
#define int i64
using vi = vector<int>;
bool isPrime(int x){
if(x < 2) return false;
for(int i = 2; i * i <= x; i ++)
if(x % i == 0) return false;
return true;
}
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int n;
cin >> n;
for(int i = 1; n > 0; i += 10)
if(isPrime(i)) cout << i << " ", n --;
return 0;
}
佔位
https://atcoder.jp/contests/nikkei2019-2-qual/tasks/nikkei2019_2_qual_d
204D
https://atcoder.jp/contests/abc204/tasks/abc204_d
令 \(m = \sum T_i\)
設 dp 的狀態轉移方程為 \(f[i][j]\) 表示前 \(i\) 個菜餚,佔用第一個烤箱時長為 \(j\) 的方案是否存在。
這樣的話直接 \(O(nm)\) 的列舉轉移就好了
#include <bits/stdc++.h>
using namespace std;
int main(){
int N;
cin >> N;
vector<int> T(N);
for (int i = 0; i < N; i++){
cin >> T[i];
}
int S = 0;
for (int i = 0; i < N; i++){
S += T[i];
}
vector<vector<bool>> dp(N + 1, vector<bool>(S + 1, false));
dp[0][0] = true;
for (int i = 0; i < N; i++){
for (int j = 0; j <= S; j++){
if (dp[i][j]){
dp[i + 1][j] = true;
dp[i + 1][j + T[i]] = true;
}
}
}
int ans = S;
for (int i = 0; i <= S; i++){
if (dp[N][i]){
ans = min(ans, max(i, S - i));
}
}
cout << ans << endl;
}
但是我們注意到對於任意一個 \(j\) 滿足 \(f[i][j]\) 成立,則一定會有 \(f[i+1][j + T_{i+1}]\) 成立。如果吧 \(f[i]\) 看成一個二進位制數,則相當於左移 \(T_{i+1}\) 位,這樣的話,我們可以是用 bitset
快速求解。
#include <bits/stdc++.h>
using namespace std;
using i32 = int32_t;
const int M = 1e5;
bitset<M + 1> f;
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int n, m = 0;
cin >> n;
f[0] = true;
for (int i = 1, t; i <= n; i++) {
cin >> t;
m += t;
f |= (f << t);
}
int res = INT_MAX;
for (int i = 0; i <= m; i++)
if (f[i]) res = min(res, max(i, m - i));
cout << res;
return 0;
}
207E
https://atcoder.jp/contests/abc207/tasks/abc207_e
令 \(f[i][j]\) 表示分成 \(i\) 段的情況下前 \(j\) 個元素的方案數。可以得到一個 \(O(n^3)\) 的轉移如下
然後 \(sum(k + 1 , j) \equiv 0 \mod{i}\) 等價與 \(\sum(1,j) \equiv \sum(1,k) \mod i\)
因此我們其實可以用 \(i\) 個字首和計算出轉移
#include <bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
#define int i64
using vi = vector<int>;
const int mod = 1e9 + 7;
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int n;
cin >> n;
vi a(n + 1);
for (int i = 1; i <= n; i++)
cin >> a[i], a[i] += a[i - 1];
vector f(n + 1, vi(n + 1));
f[0][0] = 1;
for (int i = 1; i <= n; i++) {
vector<int> cnt(i);
cnt[0] = f[i - 1][0];
for (int j = 1; j <= n; j++)
f[i][j] = cnt[a[j] % i], (cnt[a[j] % i] += f[i - 1][j]) %= mod;
}
int res = 0;
for (int i = 1; i <= n; i++)
res = (res + f[i][n]) % mod;
cout << res;
return 0;
}
074C
https://atcoder.jp/contests/abc074/tasks/arc083_a
一個滿有意思的列舉題。看似是 dp 但實際上估計範圍後,發現列舉複雜度是正確的。
#include<bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
#define int i64
const int inf = INT_MAX / 2;
i32 main() {
int A, B, C, D, E, F;
cin >> A >> B >> C >> D >> E >> F;
A *= 100, B *= 100;
int x = 1, y = 0;
for (int i = 0; A * i <= F; i++)
for (int j = 0; A * i + B * j <= F; j++) {
int p = A * i + B * j, q = 0;
if (p == 0) continue;
int T = min(F - p, p * E / 100);
for (int k = 0, l; k * C <= T; k++) {
l = (T - k * C) / D;
q = max(q, k * C + l * D);
}
if (x + y != 0 and q * x > y * (p + q)) x = p + q, y = q;
}
if (y == 0) cout << A << " 0";
else cout << x << " " << y;
return 0;
}