Educational Codeforces Round 170 (Rated for Div. 2)

PHarr發表於2024-10-16

A. Two Screens

難點是讀題,找到最長公共字首即可。

#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 i32 inf = INT_MAX / 2;

const int mod = 1e9 + 7;

void solve(){
    string s, t;
    cin >> s >> t;

    int res = s.size() + t.size();
    int i = 0;
    while(i < s.size() and i < t.size() and s[i] == t[i])
        i ++, res --;
    if(i > 0) res ++;
    cout << res << "\n";
}


i32 main() {
    ios::sync_with_stdio(false), cin.tie(nullptr);
    int T;
    cin >> T;
    while(T --) solve();
    return 0;
}

B. Binomial Coefficients, Kind Of

打表找規律

#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 i32 inf = INT_MAX / 2;

const int mod = 1e9 + 7;

i32 main() {
    ios::sync_with_stdio(false), cin.tie(nullptr);
    
    int N = 1e5;
    vi f(N + 1);
    f[0] = 1;
    for(int i = 1; i <= N; i ++)
        f[i] = f[i - 1] * 2 % mod;
    int T;
    cin >> T;
    vi n(T), k(T);
    for(auto &i : n) cin >> i;
    for(auto &i : k) cin >> i;

    for(int i = 0 ; i < T; i ++) {
        if(n[i] == k[i]) cout << "1\n";
        else cout << f[k[i]] << "\n";
    }
    return 0;
}

C. New Game

首先我們可以統計出每個數字出現的次數。

可以用雙指標求出最大的\([l,r]\),滿足區間內的數至少出現一次且\(r < l + k\)

#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 i32 inf = INT_MAX / 2;

const int mod = 1e9 + 7;

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

    map<int, int> cnt;
    for(int i = 0, x; i < n ; i ++) 
        cin >> x, cnt[x] ++;

    vector<pii> a;
    for(auto it : cnt) a.push_back(it);

    int m = a.size();
    int res = 0, sum = 0;
    for(int l = 0, r = -1; r < m;) {
        if(r < l) {
            if(l >= m) break;
            assert(sum == 0);
            sum = a[l].second , r = l;
        } 
        while(r + 1 < m and a[r + 1].first < a[l].first + k and a[r + 1].first == a[r].first + 1)
            r ++, sum += a[r].second;
        res = max(res, sum);
        sum -= a[l].second, l ++;
    }
    cout << res << "\n";
}

i32 main() {
    ios::sync_with_stdio(false), cin.tie(nullptr);
    
    int T;
    cin >> T;
    while(T --) solve();
    return 0;
}
 

D. Attribute Checks

考慮字首中\(i\)中有\(sum\)點屬性點,我們用\(f[x]\)表示當前分配\(x\)點智力,\(y = sum - x\)點力量可以滿足多少次檢查。

現在有三種情況。

如果當前是智力檢測\(r_i\),則分配了\(x\in[r_i, sum]\)\(f[x]\)均加\(1\)

如果當前是力量檢測\(r_i\),則分配了\(y\in[-r_i, sum]\)\(f[x]\)均加\(1\)

這兩種情況是區間修改。

如果當前是一個待分配的智力點,則\(f[x]\)可以從\(f[x],f[x-1]\)轉移過來。

這種情況是單點查詢。

因此我們可以用一個樹狀陣列來維護。

但是,我們注意到\(dp\)操作,因此我們可以直接新建一個樹狀陣列更新好之後再賦值回去。

\(f[x]\)的值域是\(m\),至多重建\(m\)次,因此重建的總複雜度是\(O(m^2\log m)\)

區間修改的次數是\(n\),複雜度是\(O(n\log m)\)

因此最終的複雜度就是\(O(n \log m)\)

#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 i32 inf = INT_MAX / 2;

const int mod = 1e9 + 7;

struct BinaryIndexedTree{
#define lowbit(x) ( x & -x )
    int n;
    vector<int> b;

    BinaryIndexedTree(int n) : n(n) , b(n + 1 , 0){};
    
    void modify(int i , int y) {
        for(; i <= n ; i += lowbit(i) ) b[i] += y;
        return;
    }

    void modify(int l, int r, int y) {
        l ++ , r ++;
        modify(l, y), modify(r + 1, -y);
    }

    int calc(int i){
        i ++;
        int sum = 0;
        for( ; i ; i -= lowbit(i) ) sum += b[i];
        return sum;
    }
};

i32 main() {
    ios::sync_with_stdio(false), cin.tie(nullptr);
    
    int n, m;
    cin >> n >> m;

    
    int sum = 0;

    BinaryIndexedTree f(m + 1);
    
    for(int i = 0 , x ; i < n; i ++) {
        cin >> x;
        if(x == 0) {
            sum ++;
            BinaryIndexedTree g(m + 1);
            g.modify(0 , 0, f.calc(0));
            for(int j = 1, x; j <= sum; j ++) {
                x = max(f.calc(j), f.calc(j - 1));
                g.modify(j, j, x);
            }
            f = move(g);
        } else if(x > 0) {
            if(x > sum) continue;
            f.modify(x, sum , 1);
        } else {
            x = - x;
            if(x > sum) continue;
            f.modify(0, sum - x, 1);
        }
    }

    int res = 0;
    for(int i = 0; i <= m; i ++)
        res = max(res, f.calc(i));
    cout << res;
    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 i32 inf = INT_MAX / 2;

i32 main() {
    ios::sync_with_stdio(false), cin.tie(nullptr);

    int n, m;
    cin >> n >> m;

    vi f(m + 2);
    for (int x, sum = 0; n; n--) {
        cin >> x;
        if (x == 0) {
            sum ++;
            for (int i = 1; i <= m; i++)
                f[i] += f[i - 1];
            for (int i = m; i > 0; i--)
                f[i] = max(f[i], f[i - 1]);
            for (int i = m; i > 0; i--)
                f[i] = f[i] - f[i - 1];
        } else if (x > 0) {
            if (x > sum) continue;
            f[x]++, f[sum + 1]--;
        } else {
            x = -x;
            if (x > sum) continue;
            f[0]++, f[sum - x + 1]--;
        }
    }

    for (int i = 1; i <= m; i++)
        f[i] += f[i - 1];
    cout << ranges::max(f);

    return 0;
}

E. Card Game

對於花色為\(1\)的牌,Alice 拿到的牌數一定不少於 Bob。對於花色不為\(1\)的牌,Alice 拿到的牌數一定不多於Bob。

我們在分配的時候可以一次分配兩張牌,其中較大的給 Alice,另一張給 Bob。

設 dp 狀態\(f[i][j]\)為某種花色的牌,對於前\(i\)張牌,分配了\(i-j\)張牌,剩下\(j\)張牌的方案數,因此一定有\((i-j) | 2\),也就是\(i,j\)奇偶性相同。

對於第\(i\)牌有兩種情況。如果不分配,則前\(i-1\)張牌分配了\(j-1\)張。如果分配,則需要一張之前沒有分配的牌,因此前\(i-1\)張分配了\(j+1\)張。因此有如下轉移

\[f[i][j] = f[i-1][j - 1] + f[i - 1][j + 1] \]

考慮只有第一種花色Alice可以多拿。其他花色只能 Bob 多拿,並且 Bob 每多拿一張,Alice 就要消耗一張花色 1 的牌。因此我們可以記 dp 狀態為\(g[i][j]\),表示前\(i\)種花色的牌,此時 Alice還多出來了幾張花色為 1 的牌。對於花色 1 的牌,因為只能是 Alice 多拿,因此\(g[1][i] = f[m][i]\)。對於其他花色的牌,我們可以列舉出 Bob 多拿了\(k\)張,則存在轉移

\[g[i][j] += g[i-1][j + k] * f[m][j] \]

注意這裡的\(j,k\)都只能是偶數。

對於第二個 dp 可以\(O(N^3)\)轉移即可。

#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 mod = 998244353;


struct mint {
    int x;

    mint(int x = 0) : x(x) {}

    int val() {
    	return x = (x % mod + mod) % mod;
    }

    mint &operator=(int o) { return x = o, *this; }

    mint &operator+=(mint o) { return (x += o.x) >= mod && (x -= mod), *this; }

    mint &operator-=(mint o) { return (x -= o.x) < 0 && (x += mod), *this; }

    mint &operator*=(mint o) { return x = (i64) x * o.x % mod, *this; }

    mint &operator^=(int b) {
        mint w = *this;
        mint ret(1);
        for (; b; b >>= 1, w *= w) if (b & 1) ret *= w;
        return x = ret.x, *this;
    }

    mint &operator/=(mint o) { return *this *= (o ^= (mod - 2)); }

    friend mint operator+(mint a, mint b) { return a += b; }

    friend mint operator-(mint a, mint b) { return a -= b; }

    friend mint operator*(mint a, mint b) { return a *= b; }

    friend mint operator/(mint a, mint b) { return a /= b; }

    friend mint operator^(mint a, int b) { return a ^= b; }
};

i32 main() {
	int n, m;
	cin >> n >> m;

	vector f(m + 1, vector<mint>(m + 1));
	f[0][0] = 1;
	for(int i = 1; i <= m; i ++) 
		for(int j = 0; j <= i; j ++) {
			if(i % 2 != j % 2) continue;
			if(j - 1 >= 0) f[i][j] += f[i - 1][j - 1];
			if(j + 1 <= m) f[i][j] += f[i - 1][j + 1];
		}

	vector g(n + 1, vector<mint>(m + 1));

	for(int i = 0; i <= m; i += 2) 
		g[1][i] = f[m][i];

	for(int i = 2; i <= n; i ++) {
		for(int j = 0; j <= m; j += 2) {
			for(int k = 0; k + j <= m; k += 2) {
				g[i][j] += g[i - 1][k + j] * f[m][k];
			}
		}
	}

	cout << g[n][0].val();
	return 0;
}

相關文章