AtCoder Beginner Contest 中的小思維題

PHarr發表於2024-08-23

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)\) 的轉移如下

\[f[i][j] = \sum_{k = 0}^{j-1}f[i-1][k] \times ( sum(k + 1 , j) \equiv 0 \mod{i}) \]

然後 \(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;
}

相關文章