Educational Codeforces Round 159 (Div. 2) A~E

A.Binary lmbalance(思维)

题意:

给出一个01字符串,你可以对字符串进行无限次下列操作:

  • 选择一个位置 i ( 1 ≤ i ≤ ∣ s ∣ − 1 , |s|为字符串s的长度 ) i(1 \le i \le |s| - 1,\text{|s|为字符串s的长度}) i(1is1,|s|为字符串s的长度)

    • 如果 s [ i ] ≠ s [ i + 1 ] s[i] \ne s[i + 1] s[i]=s[i+1],在 s [ i ] s[i] s[i] s [ i + 1 ] s[i + 1] s[i+1]之间插入一个'0'

    • 如果 s [ i ] = s [ i + 1 ] s[i] = s[i + 1] s[i]=s[i+1],在 s [ i ] s[i] s[i] s [ i + 1 ] s[i + 1] s[i+1]之间插入一个'1'

问:能否在经过一些操作后,使得字符串中0的数量严格大于1的数量。

分析:

只要字符串中同时存在01,那么就可以无限产生0,此时必然成立,如果字符串中没有1,那么也必然成立。

只会当字符串中只有1时,才无法使0的数量严格大于1的数量。

代码:

#include 

typedef long long LL;
using namespace std;
const int N = 3e5 + 5;


void solve() {
    int n;
    string s;
    cin >> n >> s;
    int one = 0, zero = 0;
    for (int i = 0; i < n; i++) {
        if (s[i] == '0') zero++;
        else one++;
    }
    if (one && !zero) {//只有1出现,0没有出现才是NO
        cout << "NO" << endl;
    } else {
        cout << "YES" << endl;
    }
}

int main() {
    int Case;
    cin >> Case;
    while (Case--) {
        solve();
    }
    return 0;
}

B.Getting Points(数学)

题意:

n n n天时间可以学习(第一天为周一),每天可以去上课,上课将获得 l l l点学分,每周一会布置一个课后练习,完成练习可以获得 t t t点学分。

每天可以选择休息或去上课,如果选择休息,那么无法得到当天的学习学分,且不能完成课后练习。如果选择去上课,可以获得上课的学分,且每天去上课可以在未完成的练习中选择不超过两个练习任务完成。

问:需要获得 P P P点学分才能毕业,最多可以休息多少天。

分析:

分两种情况进行考虑:

  • 1.如果每次去上课均完成两个任务,且这样就能获取足够的学分,那么就计算最少需要去上课几天。

  • 2.如果仅用最少上课天数去完成任务无法达到毕业学分,那么就需要额外的时间去上课,计算所需的额外天数。

题意:

#include 

typedef long long LL;
using namespace std;
const int N = 3e5 + 5;


void solve() {
    LL n, l, t, P;
    cin >> n >> P >> l >> t;
    LL task_cnt = n / 7 + (n % 7 != 0);//计算练习任务的数量
    LL lesson_cnt = (task_cnt + 1) / 2;//计算完成练习任务所需的天数
    LL earn = task_cnt * t + lesson_cnt * l;//每次去上课都可以获得上课的学分以及任务的学分
    if (earn >= P) {//此时已经拿够学分了
        LL one_earn =  t * 2 + l;//计算一天可以获得的学分
        LL ans = P / one_earn;//计算所需的天数
        if (ans * one_earn < P) {
            ans++;
        }
        cout << n - ans << endl;
    } else {
        P -= earn;//计算还需要获得的学分
        LL ans = lesson_cnt + P / l + (P % l != 0);//此时每天只能获得上课的学分,计算额外的天数
        cout << n - ans << endl;
    }
}

int main() {
    int Case;
    cin >> Case;
    while (Case--) {
        solve();
    }
    return 0;
}

C.Insert and Equalize(GCD)

题意:

给出一个包含 n n n个数字的数组 a a a,其中每个数字均不相同。

你需要在数组 a a a中插入一个数组 a a a中没有的数字,然后选择一个 x x x,对数组中每个数通过加上若干次 x x x后,使得所有数字均相同。

问:最少需要多少次操作,才能使得数组中所有数字均相同?

分析:

a a a数组进行排序(此时数据存储于下标 1 ∼ n 1 \sim n 1n

a [ n ] a[n] a[n]为操作完最终的数,为了使所有数字均能通过加上 x x x变为 a [ n ] a[n] a[n],且操作次数最少,那么需要对 a a a数组所有数变为 a [ n ] a[n] a[n]所需加上的值取GCD(最大公约数)。

然后考虑插入的新数字怎么选择,考虑如果选择最大的数字加上 x x x的值做为新的数字,那么此时的操作次数需要加上 n n n(原数组中 n n n个数字还需再加上)。

而如果能选择一个满足 a [ n ] − k × x ( k < n ) a[n] - k \times x(k \lt n) a[n]k×x(k<n)的一个数字,那么只需要额外 k k k次操作就能使新插入的数字也变为 a [ n ] a[n] a[n],此时操作次数一定小于前面的方案,此时的 k k k可以通过for循环进行枚举获得。

代码:

#include 

typedef long long LL;
using namespace std;
const int N = 3e5 + 5;

int n;
LL a[N];

void solve() {
   cin >> n;
   for (int i = 1; i <= n; i++) {
       cin >> a[i];
   }
   sort (a + 1, a + n + 1);
   LL x = a[n] - a[1];
   for (int i = 2; i <= n; i++) {
       x = __gcd(x, a[n] - a[i]);
   }
  x = max(1ll, x);//考虑n为1时的情况
   LL cnt = 0;
   for (int i = 1; i <= n; i++) {
       cnt += (a[n] + x - a[i]) / x;
   }
   LL ans = cnt;
   for (int i = n - 1, k = 1; i >= 1; i--, k++) {
       if (a[i] != a[n] - k * x) {//找最小的k
           ans = min(ans, cnt - n + k);
           break;
       }
   }
   cout << ans << endl;
}

int main() {
    int Case;
    cin >> Case;
    while (Case--) {
        solve();
    }
    return 0;
}

D. Robot Queries(思维+STL)

题意:

有一个无限的二维网格。一开始,机器人站在 ( 0 , 0 ) (0,0) (0,0)点。机器人可以执行四条指令:
U , D , L , R U,D,L,R U,D,L,R分别表示向上下左右走一步。现在给你一个由 U D L R UDLR UDLR构成的操作序列 s s s,以及 q q q次查询,每次查询给出 x , y , l , r x,y,l,r x,y,l,r,表示将 s [ l ] − s [ r ] s[l]-s[r] s[l]s[r]这段操作序列翻转。
询问机器人在执行命令序列 s s s时是否访问了点 ( x , y ) (x,y) (x,y)

分析:

序列不翻转的情况下,走到每一个字母对应的位置一定是固定的,这样的情况查询每个点有没有被经过只要记录一下即可,如果进行了区间翻转, 1 1 1 l − 1 l-1 l1 以及 r r r n n n 的位置仍然是不变的,只有 l l l r r r 之间进行了中心翻转,通过画图可以发现如果 ( x , y ) (x,y) (x,y)翻转之后的坐标为 ( s x [ l − 1 ] + s x [ r ] − x , s y [ l − 1 ] + s y [ r ] − y ) (sx[l-1]+sx[r]-x,sy[l-1]+sy[r]-y) (sx[l1]+sx[r]x,sy[l1]+sy[r]y),只要查询这个点所记录的所有达到这个点的位置是否在 ( l , r ) (l,r) (l,r)之间即可

代码:

#include 

using namespace std;
typedef long long LL;
int sx[200005], sy[200005];

int main() {
    int n, q;
    cin >> n >> q;
    string s;
    cin >> s;
    map<pair<int, int>, vector<int>> mp;
    int x = 0, y = 0;
    sx[0] = 0, sy[0] = 0;
    mp[{x, y}].push_back(0);
    for (int i = 0; i < n; i++) {
        if (s[i] == 'U') {
            y++;
        }
        if (s[i] == 'D') {
            y--;
        }
        if (s[i] == 'R') {
            x++;
        }
        if (s[i] == 'L') {
            x--;
        }
        mp[{x, y}].push_back(i + 1);
        sx[i + 1] = x, sy[i + 1] = y;
    }
    while (q--) {
        int x, y, l, r;
        cin >> x >> y >> l >> r;
        int ok = 0;
        if (mp[{x, y}].size() >= 1) {
            if (mp[{x, y}][0] < l || mp[{x, y}][mp[{x, y}].size() - 1] >= r) {
                ok = 1;
                cout << "YES" << endl;
                continue;
            }
        }
        int xx = sx[l - 1] + sx[r] - x;
        int yy = sy[l - 1] + sy[r] - y;
        if (mp[{xx, yy}].size() >= 1) {
            auto k = lower_bound(mp[{xx, yy}].begin(), mp[{xx, yy}].end(), l);
            if (k != mp[{xx, yy}].end() && *k < r)
                ok = 1;
        }
        if (ok == 1) {
            cout << "YES" << endl;
        } else
            cout << "NO" << endl;
    }
    return 0;
}

E. Collapsing Strings (字典树)

题意:

n n n个字符串 s [ i ] s[i] s[i] ∣ s [ i ] ∣ \vert s[i]\vert s[i]表示 s [ i ] s[i] s[i]的长度,两个字符串的合并 C ( a , b ) C(a,b) C(a,b)的运算如下:

  • 如果 a a a为空,则 C ( a , b ) = b C(a,b)=b C(a,b)=b
  • 如果 b b b为空,则 C ( a , b ) = a C(a,b)=a C(a,b)=a
  • 如果 a a a的最后一个字母等于 b b b 的第一个字母,则 C ( a , b ) = C ( a 1 , ∣ a ∣ − 1 , b 2 , ∣ b ∣ ) C(a,b)=C(a_1,|a|-1,b_2,|b|) C(a,b)=C(a1,a1,b2,b) ,其中 s l , r s_{l,r} sl,r s s s l l l 字母到 r r r 字母的子串;
  • 否则为 C ( a , b ) = a + b C(a,b)=a+b C(a,b)=a+b,即两个字符串的连接。

询问 ∑ i = 1 n ∑ j = 1 n ∣ C ( s i , s j ) ∣ \sum_{i=1}^{n}\sum_{j=1}^{n} \vert C(s_i,s_j) \vert i=1nj=1nC(si,sj)

分析:

每一个字符串都要和其他包括自己的所有字符进行 C C C操作,对于任何一个固定的字符串 s s s,它要和所有的字符串进行拼接,也就是要减去最长相等前后缀的贡献。用字典树存所有的前缀的个数。枚举所有字符串后缀从长往短搜,每次先加上当前字符串总长,再考虑容斥从答案中减去重复的答案。
记录前一个后缀匹配前缀的个数 l s t lst lst,当前后缀匹配前缀的个数 c u r cur cur,以当前后缀作为匹配后缀减去的个数为 c u r − l s t cur-lst curlst,答案减去这一部分即可。

代码:

#include 

using namespace std;
const int N = 1e6 + 5;
typedef long long LL;
int ch[N][26], cnt[N], idx;

void insert(string s) {
    int p = 0;
    for (int i = 0; i < s.size(); i++) {
        int j = s[i] - 'a'; // 字母映射
        if (!ch[p][j])
            ch[p][j] = ++idx;
        p = ch[p][j];
        cnt[p]++;
    }
}

vector<int> query(string s) {
    vector<int> v(s.size(), 0);
    int p = 0;
    for (int i = 0; i < s.size(); i++) {
        int j = s[i] - 'a';
        if (!ch[p][j])
            break;
        p = ch[p][j];
        v[i] = cnt[p];
    }
    return v;
}

int main() {
    int n;
    cin >> n;
    vector<string> s(n);
    int tot = 0;
    for (int i = 0; i < n; i++) {
        cin >> s[i];
        insert(s[i]);
        tot += s[i].size();
    }
    LL ans = 0;
    for (int i = 0; i < n; i++) {
        reverse(s[i].begin(), s[i].end());
        int len = s[i].size();
        ans += tot + 1LL * len * n;
        int lst = 0;
        auto v = query(s[i]);
        for (int j = s[i].size() - 1; j >= 0; j--) {
            int cur = v[j];
            int ad = cur - lst;
            lst = cur;
            ans -= 2 * (j + 1) * ad;
        }
    }
    cout << ans << endl;
    return 0;
}

学习交流

以下为学习交流QQ群,群号: 546235402,每周题解完成后都会转发到群中,大家可以加群一起交流做题思路,分享做题技巧,欢迎大家的加入。

Educational Codeforces Round 159 (Div. 2) A~E_第1张图片

你可能感兴趣的:(codeforces题解,算法,c++,数据结构,OI,codeforces)