Educational Codeforces Round 70 (Rated for Div. 2) 题解

比赛链接:https://codeforc.es/contest/1202
 
A. You Are Given Two Binary Strings...
题意:给出两个二进制数\(f(x)\)\(f(y)\),定义一个二进制数\(s_k=f(x)+2^k\cdot f(y)\),询问\(k\)取何值时\(s_k\)的反向字符串(将二进制数看作01串)字典序最小。

分析:首先,我们需要知道对于一个二进制数乘2,相当于将该数整体左移一位,因此我们只需要找到\(f(y)\)最右侧的1的位置\(pos\),令它去和\(f(x)\)\(pos\)左侧最近的\(y\)匹配,这样得到的反向字符串字典序必然最小。

AC代码

#include 
#define SIZE 200007
#define rep(i, a, b) for(int i = a; i <= b; ++i)
using namespace std;
typedef long long ll;
void io() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
}
ll n, m, t;
string s1, s2;
int main() {
    io(); cin >> t;
    while(t--) {
        cin >> s1 >> s2;
        int len1 = s1.length() - 1, len2 = s2.length() - 1;
        n = 0;
        while(s2[len2--] == '0') ++n;
        m = n;
        while(s1[len1 - m] == '0') ++m;
        cout << m - n << endl;
    }
}

 
B. You Are Given a Decimal String...
题意:不是很讲得清楚,可以看题中样例理解。给出一个字符串字符串中的每一位数字由确定的两个数字任意组合得到,询问,为了得到该字符串,最少需要多构成几个无效数字。例如:
给出字符串\(0840\),确定的两个数字为\((2, 4)\)

  1. 0 (初始必定为零)
  2. 04 (\(+4\)
  3. 048 (\(+4\)
  4. 0482 (\(+4\),如果超过10就只取个位数)
  5. 04824 (\(+2\)
  6. 048248 (\(+4\)
  7. 0482480 (\(+2\)

于是,(0) 4 (8) 2 (4) 8 (0),多生成的数的个数为3。

分析:从字符串第\(k\)位到\(k+1\)位实际上相当于变化了\(\vert s_{k+1}-s_k \vert\),这个值不会超过10,因此我们只需要暴力求出这10种情况所生成的无效数字个数即可。

AC代码

#include 
#define SIZE 200007
#define rep(i, a, b) for(int i = a; i <= b; ++i)
const int maxn = 1e9;
using namespace std;
typedef long long ll;
void io() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
}
int n, m, t; int dp[10];
string s;
int main() {
    io(); cin >> s;
    int len = s.length();
    rep(i, 0, 9) {
        rep(j, 0, 9) {
            rep(k, 0, 9) dp[k] = maxn;
            rep(x, 0, 9) {
                rep(y, 0, 9) {
                    if (!x && !y) continue;
                    dp[(x * i + y * j) % 10] = min(x + y - 1, dp[(x * i + y * j) % 10]);
                }
            }
            int ans = 0;
            rep(k, 0, len - 2) {
                int tmp = s[k + 1] - s[k];
                if (dp[(tmp + 10) % 10] == maxn) { ans = -1; break; }
                ans += dp[(tmp + 10) % 10];
            }
            cout << ans << ' ';
        }
        cout << endl;
    }
}

 
C. You Are Given a WASD-string...
题意:T组输入,每组输入给出一个只含“\(W, A, S, D\)”的字符串(又是字符串...)。\(W, A, S, D\)分别代表一个机器人可以向上,左,下,右四个方向移动一格。定义机器人走过的面积为能包含所有机器人走到过的格子的最小面积。现在,你能在字符串中的任意位置加入一个字符(只能为\(W, A, S, D\)),询问可能的机器人走过的最小面积。

分析:由于只能加入一个字符,所以我们发现横向的移动和纵向移动互不干扰(最小矩形只能是长或者宽缩进一个单位),因此我们只需要分成横向纵向两个子问题考虑,并找到最小面积即可。我用了一种前缀和的思想写,而标程给出了更简洁的解法。

AC代码

#include 
#define SIZE 200007
#define rep(i, a, b) for(int i = a; i <= b; ++i)
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
void io() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
}
int n, m, t;
string str;
struct node {
    int val;
    int pos;
    int la, lb, ra, rb;
}p1[SIZE], p2[SIZE];
void init1(node p[], int n) {
    int x = INF, y = -INF;
    rep(i, 0, n) {
        p[i].la = min(x, p[i].pos);
        x = min(x, p[i].la);
        p[i].ra = max(y, p[i].pos);
        y = max(y, p[i].ra);
    }
}
void init2(node p[], int n) {
    int x = INF, y = -INF;
    for (int i = n; i >= 0; --i) {
        p[i].lb = min(x, p[i].pos);
        x = min(x, p[i].lb);
        p[i].rb = max(y, p[i].pos);
        y = max(y, p[i].rb);
    }
}
int main() {
    io(); cin >> t;
    while (t--) {
        cin >> str;
        int k = 1, j = 1;
        rep(i, 0, str.length() - 1) {
            if (str[i] == 'W') p1[k++].val = 1;
            else if (str[i] == 'S') p1[k++].val = -1;
            else if (str[i] == 'A') p2[j++].val = 1;
            else p2[j++].val = -1;
        }
        rep(i, 0, k - 1) p1[i].pos = p1[i - 1].pos + p1[i].val;
        rep(i, 0, j - 1) p2[i].pos = p2[i - 1].pos + p2[i].val;
        init1(p1, k - 1); init1(p2, j - 1);
        init2(p1, k - 1); init2(p2, j - 1);
        ll w = p1[k - 1].ra, a = p2[j - 1].la, s = p1[k - 1].la, d = p2[j - 1].ra;
        ll h = p1[k - 1].ra - p1[k - 1].la + 1, wid = p2[j - 1].ra - p2[j - 1].la + 1;
        bool f1 = false, f2 = false;
        rep(i, 0, k - 1) {
            if (p1[i].lb < p1[i].la && p1[i].rb < p1[i].ra) { f1 = true; break; }
            if (p1[i].lb > p1[i].la && p1[i].rb > p1[i].ra) { f1 = true; break; }
        }
        rep(i, 0, j - 1) {
            if (p2[i].lb < p2[i].la && p2[i].rb < p2[i].ra) { f2 = true; break; }
            if (p2[i].lb > p2[i].la && p2[i].rb > p2[i].ra) { f2 = true; break; }
        }
        if (f1 || f2) {
            if (f1 && f2) {
                if (wid > h) cout << wid * (h - 1) << endl;
                else cout << (wid - 1) * h << endl;
                continue;
            }
            if (f1) cout << wid * (h - 1) << endl;
            else cout << (wid - 1) * h << endl;
        }
        else cout << wid * h << endl;
    }
}

 
D. Print a 1337-string...
题意:给出一个正整数\(n\),构造一个含有\(n\)\(1337\)子串的字符串(还是字符串...)。

分析:我们考虑每次构造\(C_n^2\)个直到完成例如 \(10=6+3+1\),那么我们就能构造出\(13373737\)。但要注意最后可能会剩下2,可是不能重复构造两次1。因此最后为2的构造我们在倒数第三位插入两个1,例如:\(8=6+2\) 构造出:\(13311337\)

AC代码

#include 
#define SIZE 200007
#define rep(i, a, b) for(int i = a; i <= b; ++i)
using namespace std;
typedef long long ll;
void io() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
}
ll n, m, t; bool flag;
ll dp[SIZE];
vector v;
int main() {
    io(); cin >> t;
    rep(i, 1, 100050) dp[i] = 1ll * i * (i - 1) / 2;
    while (t--) {
        cin >> n; m = n; v.clear();
        if (n == 2) { cout << "11337\n"; continue; }
        flag = false;
        while (n) {
            int pos = lower_bound(dp, dp + 100010, n) - dp;
            if (dp[pos] > n) --pos;
            n -= dp[pos];
            v.emplace_back(pos);
            if (n == 2) { flag = true; break; }
        }
        int cnt = 1, it = v.size() - 1;
        cout << "13";
        while (cnt < v[0]) {
            if (flag && cnt == v[0] - 2) cout << "11";
            if (cnt < v[it]) cout << 3;
            else { --it, --cnt; cout << 7; }
            cnt++;
        }
        cout << "7\n";
    }
}

 
E. You Are Given Some Strings...
题意:先给出一个字符串\(t\)(全是字符串...),再给出\(n\)个字符串\(s1, s2, ..., s_n\)。定义函数\(f(t, s)\)为求出字符串\(s\)\(t\)中的出现次数,例如:\(f(aaabacaa, aa)=3\)。现在要求出\(\sum_{i=1}^{n}\sum_{j=1}^{n}f(t, s_i+s_j)\)的值。

分析:我们考虑如果\(t\)串中存在\(s_i+s_j\)那么必然存在一个位置\(pos\)满足\(pos\)前面为后缀串\(s_i\)\(pos\)后面为前缀串\(s_j\),因此我们只需要对每一个\(pos\)跑出前缀串和后缀串的数量,乘法原理累加即可。而前缀串后缀串计数的操作只需要正反跑两次AC自动机即可。

AC代码

#include 
#define SIZE 200010
#define rep(i, a, b) for(int i = a; i <= b; ++i)
using namespace std;
typedef long long ll;
void io() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
}
struct ACAM {
    int cnt, trie[SIZE][26], fail[SIZE], q[SIZE], sum[SIZE], h, r, num[SIZE];
    void insert(char s[], int n) {
        int now = 0;
        rep(i, 1, n) {
            int p = s[i] - 'a';
            if (!trie[now][p]) trie[now][p] = ++cnt;
            now = trie[now][p];
        }
        sum[now]++;
    }
    void build() {
        h = 1; r = 0;
        rep(i, 0, 25) if (trie[0][i]) q[++r] = trie[0][i];
        while (h <= r) {
            int u = q[h++];
            rep(i, 0, 25) {
                if (trie[u][i]) {
                    fail[trie[u][i]] = trie[fail[u]][i];
                    sum[trie[u][i]] += sum[fail[trie[u][i]]];
                    q[++r] = trie[u][i];
                }
                else trie[u][i] = trie[fail[u]][i];
            }
        }
    }
    void solve(char s[], int n) {
        int now = 0;
        rep(i, 1, n) {
            int p = s[i] - 'a';
            now = trie[now][p];
            num[i] = sum[now];
        }
    }
}AC[2];
int n, len;
char s[SIZE], t[SIZE], rs[SIZE], rt[SIZE];
int main() {
    io(); cin >> (t + 1) >> n;
    rep(i, 1, n) {
        cin >> (s + 1);
        len = strlen(s + 1);
        for (int i = len; i; --i) rs[len - i + 1] = s[i];
        AC[0].insert(s, len);
        AC[1].insert(rs, len);
    }
    len = strlen(t + 1);
    for (int i = len; i; --i) rt[len - i + 1] = t[i];
    AC[0].build(); AC[1].build();
    AC[0].solve(t, len); AC[1].solve(rt, len);
    ll ans = 0;
    rep(i, 1, len) ans += 1ll * AC[0].num[i] * AC[1].num[len - i];
    cout << ans;
}

 
F. You Are Given Some Letters...
题意:给出两个正整数\(a\)代表\(A\)字符的数量,\(b\)代表\(B\)字符的数量,构造一个字符串(Stringforces...),使得字符串由某一循环节或循环节中的部分构成的,询问最多能找出几组能完成上述构造的不同循环节(仅长度不同的循环节被视作不同的)。

分析:我们首先把问题转化为数学形式。先设一个循环节的长度为\(k\),那么该循环节完整出现的次数\(n\)的表达式即为\(n=\lfloor \frac{a+b}{k} \rfloor\)

同时,我们假设该循环节中\(A\)出现的次数为\(num_a\)\(B\)\(num_b\),显然有\(num_a+num_b=k\)。并且我们可以得到下列不等式组:\[0 \leq a-n \cdot num_a \leq num_a\] \[0 \leq b-n \cdot num_b \leq num_b\]

转化后得到:\[\lceil \frac{a}{n+1} \rceil \leq num_a \leq \lfloor \frac{a}{n} \rfloor\] \[\lceil \frac{b}{n+1} \rceil \leq num_a \leq \lfloor\frac{b}{n} \rfloor\]

于是,我们得到了\(num_a\)\(num_b\)的范围,当且仅当\(num_a\)\(num_b\)均存在时,我们才可以满足条件的循环节。因此,我们不妨枚举循环节出现次数\(n\)的值,通过求得的\(num_a\)\(num_b\)的存在范围来求解。

显然每次枚举,我们都应该将答案加上\(num_{amax}+num_{bmax}-num_{amin}-num_{bmin}+1\),即\(\lceil \frac{a}{n+1} \rceil + \lceil \frac{b}{n+1} \rceil - \lfloor \frac{a}{n} \rfloor - \lfloor \frac{b}{n} \rfloor + 1\),但要注意我们枚举了\(n\)的值,因此\(k\)是有范围的,\(\frac{a+b}{n+1} \leq k < \frac{a+b}{n}\)。时间复杂度\(o(\sqrt{n})\)

AC代码

#include 
#define SIZE 200007
#define rep(i, a, b) for(int i = a; i <= b; ++i)
using namespace std;
typedef long long ll;
void io() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
}
ll a, b;
string s;
int main() {
    io(); cin >> a >> b;
    ll ans = 0;
    for (ll i = 1; i <= a + b;) {
        ll n = (a + b) / i;
        ll maxr = (a + b) / n;
        if (n > a || n > b) { i = (a + b) / n + 1; continue; }
        ll l = ceil(1.0*a / (n + 1)), r = a / n;
        ll L = ceil(1.0*b / (n + 1)), R = b / n;
        if (l <= r && L <= R) ans += max(0ll, min(maxr, r + R) - max(i, l + L) + 1);
        i = maxr + 1;
    }
    cout << ans;
}

你可能感兴趣的:(Educational Codeforces Round 70 (Rated for Div. 2) 题解)