Contestant(alt). Rank 3678. Rating +62(+262 -200).

A. Is It a Cat?

题意

给定一个字符串,转化为小写字母后,判断其是否由 \(m, e, o, w\) 组成,四个字母可以出现重复多个,但顺序不能改变,且每一个字母必须出现至少一次。如 \(MeOooOW\)

思路

如题,我们只需用一个变量存储当前遍历到了哪个字母,若比较到某一个字母的时候,遍历的下标越界,那么输出 \(NO\)。否则,在最后判断一下四个字母是否都出现了至少一次,若满足那么 \(YES\),否则 \(NO\)

时间复杂度:\(O(n)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>

const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353;

int a[N];

signed main() {
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    int t;
    cin >> t;
    while(t --){
        int n;
        cin >> n;
        string s;
        cin >> s;
        int tp = 0;
        char up[4] = {'M', 'E', 'O', 'W'}, lo[4] = {'m', 'e', 'o', 'w'};
        bool ok[4] = {false};
        bool f = true;
        for(int i=0;i<n;i++){
            while(tp < 4 && s[i] != up[tp] && s[i] != lo[tp]) tp ++;
            if(tp >= 4){
                f = false;
                break;
            }
            ok[tp] = true;
        }
        cout << (ok[0] && ok[1] && ok[2] && ok[3] && f ? "YES\n" : "NO\n");
    }
}

瞎模拟即可

B. Count the Number of Pairs

题意

给定一个字符串以及一个整数 \(k\),规定同一个字母的大写和小写可以进行配对,一个字符只能出现在一对数中。定义一次操作为选择一个字符改变它的大小写,输出在小于等于 \(k\) 次的操作后,最多有多少对数。

思路

我们用两个数组解决这个问题:

  1. 令小写字母对应权值为 \(1\),大写字母对应权值为 \(-1\),那么加权后每种字母的总和的绝对值可以表征配对了多少对。我们记它为 \(abs(ans[i])\)

  2. 统计每一种数出现的次数 \(cnt[i]\)

那么,在不进行操作的前提下,我们易得最后的答案为 \(\sum \frac{(cnt[i] - abs(ans[i])}{2}\)

若可以进行操作,那么对于一个字母剩余的数 \(ans[i]\),我们可以配对 \(\frac{ans[i]}{2}\) 个。

因此,最后的答案即为 \(\sum \frac{(cnt[i] - abs(ans[i])}{2} + min(k, \sum \frac{ans[i]}{2})\)

时间复杂度:\(O(n)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>

const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353;

int a[N];

signed main() {
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    int t;
    cin >> t;
    while(t --){
        int n, k;
        cin >> n >> k;
        string s;
        cin >> s;
        int cnt[26]{}, ans[26]{};
        for(int i=0;i<n;i++){
            if(s[i] >= 'a' && s[i] <= 'z') {
                cnt[s[i] - 'a'] ++;
                ans[s[i] - 'a'] ++;
            }else{
                cnt[s[i] - 'A'] ++;
                ans[s[i] - 'A'] --;
            }
        }
        int res = 0, left = 0;
        for(int i=0;i<26;i++){
            res += (cnt[i] - abs(ans[i])) / 2;
            left += abs(ans[i]) / 2;
        }
        cout << res + min(k, left) << '\n';
    }
}

总感觉类似的题哪里做过

C1. Powering the Hero (easy version)

详见C2,区别是C1的数据量更小一点

C2. Powering the Hero (hard version)

题意

给定 \(n\) 个卡牌对应的数字,从前向后遍历,若数字不为 \(0\),那么可以选择是否将该数放在堆顶,或者将其丢弃。对于所有为 \(0\) 的数字,从堆顶拿出一个牌作为这个 \(0\) 的加分值,并将这两个牌丢弃,若堆为空则不考虑。输出最后加分的最大值。

思路

既然可以选择放或不放,那么我们一定可以让后面的 \(cnt\)\(0\) 拿到前面的前 \(cnt\) 大的数。

因而,我们直接用优先队列即可。

有趣的是,困难版的数据量也无碍。

时间复杂度:最坏\(O(n \log n)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>

const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353;

int a[N];

signed main() {
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    int t;
    cin >> t;
    while(t --){
        int n;
        cin >> n;
        priority_queue<int> q;
        int ans = 0;
        for(int i=0;i<n;i++) {
            int cur;
            cin >> cur;
            if(cur != 0) q.push(cur);
            else if(!q.empty()) ans += q.top(), q.pop();
        }
        cout << ans << '\n';
    }
}

赛时贪错了,吃了WA

D. Remove Two Letters

题意

给定一个字符串,输出去掉连续的两个字母后,得到的所有字符串中不同的字符串的个数。

思路

我们不妨取补集:

对于一个序列,若要出现重复的子串,那么需要满足 \(s[i] == s[i + 2]\),这样的话,去掉 \(s[i], s[i + 1]\) 和去掉 \(s[i + 1], s[i + 2]\) 就是等价的了。

因而,我们统计出对数,用 \(n - 1\) 减去即可。

时间复杂度:\(O(n)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>

const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353;

int a[N];

signed main() {
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    int t;
    cin >> t;
    while(t --){
        int n;
        cin >> n;
        string s;
        cin >> s;
        int ans = 0, cnt = 1;
        char pre0 = s[0], pre1 = s[1];
        for(int i=1;i<n-1;i++){
             char cur0 = s[i], cur1 = s[i + 1];
             if((pre0 == cur0 && pre1 == cur1) || pre0 == cur1 && pre1 == cur0){
                 cnt ++;
             }else{
                 ans += cnt - 1;
                 cnt = 1;
                 pre0 = cur0, pre1 = cur1;
             }
        }
        cout << n - (ans + cnt - 1) - 1 << '\n';
    }
}

赛时的代码居然还写的麻烦了一点((

E1. Unforgivable Curse (easy version)

详见E2,区别是本题k=3

E2. Unforgivable Curse (hard version)

题意

给定两个长度相等的字符串 \(s, t\),定义操作为交换 \(i, i + k\)\(i, i + k + 1\) 上的数,在任意数量的操作后,输出是否可以将 \(s\) 更改为 \(t\)

思路

首先,我们只需进行下面的操作即可将相邻数交换:

  1. \(i, i + k + 1\)

  2. \(i + k + 1, i + 1\)

  3. \(i, i + k + 1\)

因而,只要可以进行上面的操作(或者向左),那么我们只需进行一定的操作,肯定可以将其换到我们需要的为止。

那么,我们也不难发现,若要向右交换,那么 \(i > k - n\),若要想左交换,那么 \(i < k\)

因而,只要 \([k, k - n]\) 内的字符相等,就一定有解。

时间复杂度:\(O(n)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>

const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353;

int a[N];

void solve(){
    int n, k;
    cin >> n >> k;
    string s, t;
    cin >> s >> t;
    if(n <= k) cout << (s == t ? "YES\n" : "NO\n");
    else{
        bool f = true;
        if(2 * k > n){
            for(int i=n-k;i<k;i++)
                if(s[i] != t[i]) f = false;
        }
        if(f){
            sort(s.begin(), s.end());
            sort(t.begin(), t.end());
            f = s == t;
        }
        cout << (f ? "YES\n" : "NO\n");
    }
}

signed main() {
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    int t;
    cin >> t;
    while(t --) solve();
}

比赛快结束的时候就懒得写了(其实还挺好写的

F. Dasha and Nightmares

题意

给定 \(n\) 个字符串,输出有多少对字符串,满足下面的条件:

  1. 两个字符串的长度总和为奇数;

  2. 不同字母的个数为 \(25\)

  3. 每种字母出现的次数为奇数。

思路

首先,第一个条件可以忽略,因为偶数个奇数相加,一定是奇数。

其次,第二个条件即为排除掉一个字母,需要其余的字母均出现。

考虑到 \(25\) 足够小,我们不妨用状压。

更具体地说,我们枚举不出现的字母 \(x\),然后枚举所有字符串,对于字符串 \(s_i\),它的所有字母的出现次数均可以预处理。但,因为我们只需考虑奇偶性,所以我们不妨令奇数状态为 \(1\),偶数状态为 \(0\),按照字母表顺序构建一个 \(26\) 位二进制字符串,便可以得到这个字符串的"哈希"值。

那么,考虑到异或的可逆性,对于一个排除掉 \(x\) 的拼接后的字符串,它的哈希值一定为形如 \(111...0...111\)\(0\) 位即为 \(x\)。那么,对于这个 \(p = (1 << 26)\ XOR\ (1 << x)\),我们可以将其和 \(s_i\) 的哈希值进行异或,得到需要和其配对的字符串的哈希值。

显然,对于第 \(i\) 个字符串,我们只需遍历前 \(i - 1\) 个字符串是否可以与其配对即可。那么,我们可以用 \(map\) 存储一个哈希值对应的字符串有多少个,最后统计总和即可。

时间复杂度:\(O(25n + m) = 1e7\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>

const int N = 5e6 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353;

int odd[N], num[N];

void solve(){
    int n;
    cin >> n;
    for(int i=0;i<n;i++){
        string cur;
        cin >> cur;
        for(char e : cur){
            odd[i] ^= (1 << (e - 'a'));
            num[i] |= (1 << (e - 'a'));
        }
    }
    int ans = 0;
    for(int i=0;i<26;i++){ //不选i
        map<int, int> cnt;
        int tot = ((1 << 26) - 1) ^ (1 << i);
        for(int j=0;j<n;j++){
            if(!(num[j] & (1 << i))){
                ans += cnt[tot ^ odd[j]];
                cnt[odd[j]] ++;
            }
        }
    }
    cout << ans << '\n';
}

signed main() {
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    int t = 1;
    //cin >> t;
    while(t --) solve();
}

我好若,居然想不到状压

G. Symmetree

题意

给定一棵根为 \(1\) 的树,判断其是否镜像对称。

思路

也就是说,我们需要枚举所有子树,并进行同构的配对。

暴力复杂度是 \(O(n ^ 2)\) 的,因为我们需要同步枚举对应的点,每个路径会被访问多次。

但是,如果我们将判断优化到 \(O(\log n)\),就可以快很多了。

这里考虑树上哈希。

我们从上往下 \(\mathtt{dfs}\),并给不同的子树用 \(map\) 的方式标号,最后每个点都会有一个子树标号的集合,我们判断所有标号中出现奇数次的树是否对称即可。

时间复杂度:\(O(n \log n)\)

对应AC代码

#define chatgpt3_5 "bits/stdc++.h"
#define chatgpt4 "bits/extc++.h"

#include chatgpt3_5

using namespace std;

//#define FLOATING_OCEAN

#define int long long
#define pii pair<int, int>
#define pipi pair<pii, pii>
#define tpi tuple<int, int, int>
#define fs first
#define sc second
#define pb emplace_back
#define ep emplace
#define rall(x) x.rbegin(),x.rend()
#define all(x) x.begin(),x.end()

const int N = 1e6 + 10, M = 2e5 + 10, mod = 1e9 + 7, inf = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-9, PI = acos(-1);

void solve() {
    int n;
    cin >> n;
    vector<vector<int>> e(n + 1);
    for(int i=1;i<n;i++){
        int u, v;
        cin >> u >> v;
        e[u].pb(v), e[v].pb(u);
    }
    map<vector<int>, int> hs;
    map<int, bool> st;
    int id = 0;
    auto dfs = [&](auto dfs, int x, int p) -> int{
        vector<int> child;
        for(auto y : e[x]){
            if(y == p) continue;
            child.pb(dfs(dfs, y, x));
        }
        sort(all(child));
        if(!hs.count(child)) {
            map<int, int> cnt;
            for (auto y: child) {
                cnt[y]++;
            }
            int odd = 0, bad = 0;
            for (auto [y, t]: cnt) {
                if (t & 1) {
                    odd++;
                    if(!st[y]) bad ++;
                }
            }
            st[++ id] = odd < 2 && bad == 0;
            hs[child] = id;
        }
        return hs[child];
    };
    cout << (st[dfs(dfs, 1, -1)] ? "YES\n" : "NO\n");
}

signed main() {
# ifdef FLOATING_OCEAN
    freopen("1.in","r",stdin);
    freopen("1.out","w",stdout);
# endif
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
//    init();
    int t = 1;
    cin >> t;
    while (t--) solve();
}

用上哈希直接成裸题了啊(?