Educational Codeforces Round 170 (Rated for Div. 2)

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还多出来了\(j\)张花色为 1 的牌。对于花色 1 的牌,只能是 Alice 多拿,所以\(g[1][j] = f[m][j]\)。对于其他花色的牌,我们可以枚举出 Bob 多拿了\(k\)张,则存在转移

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

注意这里的\(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;
}

posted @ 2024-10-16 15:31  PHarr  阅读(180)  评论(0编辑  收藏  举报