AtCoder Beginner Contest 285

A - Edge Checker 2 (abc285 a)

题目大意

给定如下一棵树。

一棵树

给定 \(a,b(a<b)\),问两者是否有连边。

解题思路

观察数可发现其为二叉树,两者有连边当且仅当\(b=2a\)\(b=2a+1\)

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    int a, b;
    cin >> a >> b;
    if (a * 2 != b && a * 2 + 1 != b)
        cout << "No" << '\n';
    else 
        cout << "Yes" << '\n';

    return 0;
}



B - Longest Uncommon Prefix (abc285 b)

题目大意

给定一个长度为\(n\)字符串\(s\),对于每个\(i \in [1, n - 1]\) ,找到最大的\(l\)使得

  • \(i + l \leq N\)
  • \(\forall k \in [1, l], s_k \neq s_{k + i}\)

解题思路

数据范围不大,暴力即可。

即对于每个\(i\), 枚举 \(l\)\(1\)到第一个 \(s_l \neq s_{k+l}\)为止。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    int n;
    string s;
    cin >> n >> s;
    auto solve = [&](int x){
        int ans = 0;
        while(ans < n - x){
            if (s[ans] == s[ans + x])
                break;
            ++ ans;
        }
        return ans;
    };
    for(int i = 1; i < n; ++ i){
        cout << solve(i) << '\n';
    }

    return 0;
}



C - abc285_brutmhyhiizp (abc285 c)

题目大意

有一堆字符串,其字典序顺序为:

  • 'a', 'b', ..., 'z', 'aa', 'ab', ..., 'az', 'ba', 'bb', ..., 'bz', ..., 'za', 'zb', ..., 'zz', 'aaa', 'aab', ..., 'aaz', 'aba', 'abb', ..., 'abz', ..., 'zzz', 'aaaa', ...

现给定其中一个字符串,问其字典序大小。

解题思路

这题是abc171 c的反过来形式。

每个位置,以a-z的26为一循环,但第一次循环时还有一个0a可以看成是0a)。所以 a应当看成数字\(1\)

因此高位转低位时,进位时\(26\),但末尾数字减去'A'后还要加一。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    string s;
    cin >> s;
    LL ans = 0;
    for(auto &i : s){
        ans = ans * 26 + (i - 'A' + 1);
    }
    cout << ans << '\n';

    return 0;
}



D - Change Usernames (abc285 d)

题目大意

给定\(n\)对字符串 \(s_i,t_i\)。表示有 \(n\)个人,原本叫 \(s_i\),现在需要改名为 \(t_i\)

你现在要处理该请求,每次只能改一个人的名,问改名过程中会不会出现重名,即一个人改成\(t_i\)了,但还有另一个还没改名\(s_j = t_i\)

解题思路

字符串\(s_i\)改成 \(t_i\),则从 \(s_i\)连一条有向边到 \(t_i\)

如果存在一个环,则改名过程必定重名。

有向边判环的方法即拓扑排序。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    int n;
    cin >> n;
    vector<string> s(n), t(n);
    set<string> qwq;
    map<string, int> tr;
    for(int i = 0; i < n; ++ i){
        cin >> s[i] >> t[i];
        qwq.insert(s[i]);
        qwq.insert(t[i]);
    }
    int cnt = 0;
    for(auto &i : qwq){
        tr[i] = cnt;
        ++ cnt;
    }
    vector<int> du(cnt);
    vector<vector<int>> edge(cnt);
    for(int i = 0; i < n; ++ i){
        int u = tr[s[i]];
        int v = tr[t[i]];
        edge[u].push_back(v);
        du[v] ++;
    }
    queue<int> team;
    for(int i = 0; i < cnt; ++ i){
        if (du[i] == 0)
            team.push(i);
    }
    while(!team.empty()){
        auto u = team.front();
        -- cnt;
        team.pop();
        for(auto &v : edge[u]){
            du[v] --;
            if (du[v] == 0)
                team.push(v);
        }
    }
    if (cnt)
        cout << "No" << '\n';
    else 
        cout << "Yes" << '\n';


    return 0;
}



E - Work or Rest (abc285 e)

题目大意

一周有\(n\)天。 给定一个长度为\(n\)的数组 \(a\)

你需要指定 至少一天为休息日,其余为工作日。要求最大化工作日的产能。

一个工作日的产能定义为\(a_\min(x,y)\),其中 \(x\)是该日距离过去最近的休息日的天数, \(y\)是该日距离未来最近的休息日天数。注意周与周之间是连贯的,第一周的第一天的过去可以是上一周。

解题思路

如果不考虑连贯的影响,即认为一周是独立的,可以设\(dp[i]\)表示第 \(i\)天为休息日的情况下最大的产生值。转移时枚举上一次休息日的天数 \(j\)

观察 \(j|j+1,j+2,...,i-1|i\),中间 \(j+1\)\(i-1\)的产能值,即为 \(a_1,a_2,...,a_2,a_1\),即下标是一个先递增后递减的规律,就是一个 \(a\)的前缀和的两倍,根据奇偶性可能多出一个\(a_i\)。这样转移可以 \(O(1)\)。复杂度就是 \(O(n^2)\)

但是一周并不是独立的,这样的方程具有后效性,归根结底就在于我们不知道一周第一次的休息日的日子,无法算出该日子之前和最后一个休息日之后的这几天的产能贡献。但如果设\(dp[i][j]\)表示第一次休息日在第 \(i\)天,然后在第 \(j\)天也是休息日的最大产能,其复杂度为 \(O(n^3)\)

但是,注意到天与天之间没有区别的,意思就是说,如果我们确定了第一次休息日的天数\(a\),以及最后一次休息日的天数\(b\),那这两个休息日之间,里面休息日任意选,获得的产生最大值,其实就是 \(dp[b-a]\)

因此我们预处理 \(dp[i]\),然后枚举第一次休息日\(a\)和最后一次休息日 \(b\)所在天数,那答案 \(dp[b-a]\)加上前 \(a\)天和后 \(b\)天工作日的产能贡献了。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    int n;
    cin >> n;
    vector<LL> a(n);
    for(auto &i : a)
        cin >> i;
    vector<LL> sum(n);
    partial_sum(a.begin(), a.end(), sum.begin());
    vector<LL> dp(n + 1, 0);
    auto solve = [&](int len){
        int dig = (len & 1);
        int half = (len >> 1);
        LL tmp = 0;
        if (half)
            tmp += 2ll * sum[half - 1];
        if (dig)
            tmp += a[half];
        return tmp;
    };
    for(int i = 1; i <= n; ++ i){
        for(int j = 0; j < i; ++ j){
            dp[i] = max(dp[i], dp[j] + solve(i - j - 1));
        }
    }
    LL ans = 0;
    for(int i = 0; i < n; ++ i)
        for(int j = i; j < n; ++ j){
            ans = max(ans, dp[j - i] + solve(i + n - j - 1));
        }
    cout << ans << '\n';


    return 0;
}



F - Substring of Sorted String (abc285 f)

题目大意

给定一个字符串\(s\),定义字符串 \(t\)为串 \(s\)各字母从小到大排序的字符串。进行 \(q\)次操作,操作分两种:

  • 1 x c,令 \(s_x = c\)
  • 2 x y,问\(s[x..y]\)是不是 串\(t\)的一个子串

解题思路

对于询问,其等价于:串\(a\)是串 \(t\)的子串,说明

  • 其串是递增的
  • 除首字母和末字母之外,中间的字母出现次数都是串 \(s\)中出现的次数

初步想法是预处理出\(l[i],r[i]\)数组表示第 \(i\)个字母的左边、右边第一个非该字母的位置。

对于一个询问 \(2\)\(s[r[x], l[y]]\)就是中间字母,其长度和各字母的出现次数都必须符合上述要求,也就是说这段字符串必须是确定的某一个,可以用字符串hash来判断,而由于操作一涉及到修改(该确定的字符串的hash可以在\(O(26)\)的复杂度计算出),因此需要用线段树hash的方式维护其hash值。

同时\(l[i],r[i]\)数组同样也要维护,考虑操作一的影响,这涉及到区间修改,需要用线段树维护,但逻辑稍微复杂,写起来比较麻烦。

神奇的代码



G - Tatami (abc285 g)

题目大意

给定一个\(h \times w\)的网格,格子上有 1 2 ?这三个符号,要求用\(1\times 1\)\(1\times 2\)(可旋转)的矩形覆盖。其中 1的格必须被 \(1\times 1\)的格子覆盖, 2的格必须被\(1 \times 2\)的格子覆盖, ?的则随意。

问是否存在一种方案。

解题思路

<++>

神奇的代码



Ex - Avoid Square Number (abc285 h)

题目大意

给定\(n\)和一个长度为 \(k\)的序列 \(E\),要求求一个长度为 \(n\)的正整数序列的数量,满足:

  • 所有元素都不是平方数
  • 所有元素的乘积为 \(\prod_{i=1}^{K}p_i^{E_i}\)

其中 \(p_i\)是第 \(i\)小的质数。

解题思路

<++>

神奇的代码



posted @ 2023-01-16 00:50  ~Lanly~  阅读(547)  评论(2编辑  收藏  举报