Educational Codeforces Round 101 (Rated for Div. 2)

A. Regular Bracket Sequence (CF 1469 A)

题目大意

给定一个包含若干个\(?\)和分别一个的\((\)\()\)的字符串,问能否将\(?\)变成\((\)\()\),是字符串成为一个合法的括号字符串。

解题思路

注意只有一个\((\)\()\)

长度奇数不可。

\()\)于首位不可。

\((\)于末尾不可。

其余均可。

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

template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

int main(void) {
    int kase; read(kase);
    for (int ii = 1; ii <= kase; ii++) {
        string a;
        cin >> a;
        if (a[0] == ')'){
            cout << "NO" << endl;
            continue;
        }
        if (a[a.size() - 1] == '('){
            cout << "NO" << endl;
            continue;
        }
        if (a.size() & 1) cout << "NO" << endl;
        else cout << "YES" << endl;        
    }
    return 0;
}


B. Red and Blue (CF 1469 B)

题目大意

给定两个数组\(a\)\(b\),现在要求组成一个数组\(c\),其中是\(a\)里的元素的相对位置不变,\(b\)同理,使得\(c\)的前缀和最大值最大。求最大值。

解题思路

注意到最大值就来自于\(a\)的某前缀和加上\(b\)的某前缀和,枚举求最值就可以了。

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

template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

int main(void) {
    int kase; read(kase);
    for (int ii = 1; ii <= kase; ii++) {
        int n;
        read(n);
        vector<int> a(n + 1);
        for(int i = 1; i <= n; ++ i) read(a[i]);
        int m;
        read(m);
        vector<int> b(m + 1);
        for(int i = 1; i <= m; ++ i) read(b[i]);
        vector<int> suma(n + 1), sumb(m + 1);
        suma[0] = sumb[0] = 0;
        for(int i = 1; i <= n; ++ i) 
            suma[i] = a[i] + suma[i - 1];
        for(int i = 1; i <= m; ++ i) 
            sumb[i] = b[i] + sumb[i - 1];
        int ans = 0;
        for(int i = 0; i <= n; ++ i){
            for(int j = 0; j <= m; ++ j){
               ans = max(ans, suma[i] + sumb[j]);
            }
        }  
        write(ans, '\n');                 
    }
    return 0;
}


C. Building a Fence (CF 1469 C)

题目大意

给了\(n\)个宽为\(1\),高为\(h\)的栅栏,以及凹凸不平的地,长度为\(n\),先要求将栅栏放到这地上,要求

  • 第一个栅栏和最后一个栅栏只能放到地上
  • 中间的栅栏最多可以高于地面\(k - 1\)的高度放置
  • 相邻栅栏至少有高度1的单位是毗邻的

问是否存在放置要求满足以上方案。

解题思路

第一个栅栏的放置高度是固定的,进而第二个栅栏放置高度有确定的范围,且下一个栅栏可放置的高度范围只与前一个栅栏有关,所以就直接维护可以当前栅栏放置高度的范围,为当前允许范围交上上一个栅栏的限制范围,到最后一个栅栏看看放置的高度在不在范围内即可。

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

template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

int main(void) {
    int kase; read(kase);
    for (int ii = 1; ii <= kase; ii++) {
        int n, k;
        read(n);
        read(k);
        vector<LL> qwq(n);
        for(auto &i : qwq) read(i);
        LL l = qwq[0], r = qwq[0] + k;
        int sign = true;
        for(int i = 1; i < n - 1; ++ i){
            if (l >= qwq[i] + k - 1 + k) sign = false;
            if (r <= qwq[i]) sign = false;
            l = max(qwq[i], l - k + 1);
            r = min(r - 1, qwq[i] + k - 1) + k;
        }
        if (l >= qwq[n - 1] + k) sign = false;
        if (r <= qwq[n - 1]) sign = false;
        if (sign) puts("YES");
        else puts("NO");
    }
    return 0;
}


D. Ceil Divisions (CF 1469 D)

题目大意

给定一个有\(n\)个数的\(a\)数组,每次操作选择两个数\(x, y (x \neq y)\),使得\(a_x = \left\lceil \frac{a_x}{a_y} \right\rceil\)

要求执行不多于\(n + 5\)个操作,将\(a\)数组变成有n-1$个\(1\)\(1\)\(2\)的数组。输出依次进行的操作,不要求最小化。

解题思路

自然的想法就是把\(x\)分别取\(3,4,...,n-1\)\(y=n\),最后取\(x=n\)\(y=2\),但这样的操作数是\(n - 3 + \log_{2}n\)\(n\)较大时,超过了\(n + 5\)

因为\(x\)\(n\)\(y\)\(2\)的操作数过多,我们设法让\(y\)变大一点,比如\(y\)\(15\),这样\(\log_{15}n\)就不超过5了,而\(\log_{2}15\)也最多\(4\),多试几个数就能过了。(想精确的可以列出式子\(\log_{x}n + \log_{2}x\)求个最值)

也就是\(x=3,4,...,b-1,b+1,...,n-1\)\(y=n\),然后\(x=n\),\(y=b\),直到第\(n\)个数变成\(1\),然后\(x=b\),\(y=2\),直到第\(b\)个数变成\(1\)

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

template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

int main(void) {
    int kase; read(kase);
    for (int ii = 1; ii <= kase; ii++) {
        int n;
        read(n);
        vector<pair<int,int>> ans;
        int base = 15;
        for(int i = 3; i < n; ++ i){
            if (i == base) continue;
            ans.push_back({i, n});
        }
        int qwq = n;
        if (n > base){
            while(n != 1){
                ans.push_back({qwq, base});
                n = ceil(n * 1.0 / base);
            }
            int x = base;
            while(x != 1){
                ans.push_back({base, 2});
                x = ceil(x * 1.0 / 2);
            }
        }else{
            while(n != 1){
                ans.push_back({qwq, 2});
                n = ceil(n * 1.0 / 2);
            }
        }
        write(ans.size(), '\n');
        for(auto i : ans){
            printf("%d %d\n", i.first, i.second);
        }
    }
    return 0;
}


E. A Bit Similar (CF 1469 E)

题目大意

给定一个长度为\(n\)\(01\)字符串\(s\),要求构造一个长度为\(k\)\(01\)字符串\(t\),使得\(t\)与每一个长度为\(k\)\(s\)子串\(s[1..k], s[2..k+1], \dots, s[n-k+1..n]\)有一点相似。求字典序最小的字符串\(t\)

两个长度为\(k\)的字符串\(a,b\),如果存在一位\(i \in [1, k]\),有\(a_i = b_i\),则这两个字符串有一点相似

解题思路

考虑\(t\)串的第一个位置能否填\(0\),取决于剩下位能够使得和所有子串有一点相似,而这样考虑的话我们得储存已经和哪些子串有一点相似的信息,不行。

由于有一点相似的条件是任意的一位,我们考虑它的逆否命题,即不能是该子串的\(01\)翻转子串。

那么原题就变成给出了\(n - k + 1\)个被禁止的\(01\)子串,求未被禁止的字典序最小的\(01\)串是什么。

虽然\(n\)\(10^6\),但我们注意到\(\lceil log_{2}10^{6} \rceil = 20\),也就是说它最多能禁止20位的子串,所以我们只用考虑长度最长为\(20\)的子串,剩下的\(k - 20\)位全部为\(0\)即可。

所以我们就找出这\(n - k + 1\)个被禁止的\(01\)子串的后\(20\)位,把它\(hash\)成一个数,丢到禁止列表里,最后再看最小的不在禁止列表里的数是多少即可。

值得注意的是,由于我们前\(k - 20\)位全部为\(0\),所以只有当该子串的前\(k - 20\)位全部为\(1\)时,我们才把后\(20\)位子串丢进禁止列表里,因为如果有\(0\)的话,本身就有一点相似,不受后\(20\)位限制。

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

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); 
    cout.tie(0);
    int kase; cin>>kase;
    for (int ii = 1; ii <= kase; ii++) {
        int n, k;
        string s;
        cin >> n >> k >> s;
        int len = min(k, 20);
        int require = k - len;
        int st = k - len + 1;
        unordered_set<int> sign;
        int cur = 0;
        int cnt = 0;
        for(int i = 0; i < st - 1; ++ i)
            cnt += (s[i] - '0') == 0;
        for(int i = st - 1; i < st + len - 1; ++ i){
            cur <<= 1;
            cur |= (s[i] - '0') ^ 1;
        }
        if (require == 0 || cnt == 0)
            sign.insert(cur);
        int leadzero = st - 1;
        for(int i = st + len - 1; i < (int)s.size(); ++ i){
            cur <<= 1;
            cur |= (s[i] - '0') ^ 1;
            cur &= ((1 << len) - 1);
            if (require != 0) cnt = cnt - ((s[leadzero - st + 1] - '0') == 0) + ((s[leadzero] - '0') == 0);
            ++ leadzero;
            if (require == 0 || cnt == 0)
                sign.insert(cur);
        }
        int ans = 0;
        while(sign.find(ans) != sign.end()) 
            ++ ans;
        if (ans == (1 << len)){
            cout << "NO\n";
            continue;
        }else{
            cout << "YES\n";
            string left(k - len, '0');
            string right;
            while(ans){
                right+=char((ans & 1) + 48);
                ans >>= 1;
            }
            while((int)right.size() < len) right += '0';
            reverse(right.begin(), right.end());
            cout << left + right << endl;
        }
    }
    return 0;
}


F. Power Sockets (CF 1469 F)

题目大意

给定\(n\)条链长度分别为\(l_1, l_2, ..., l_n\),现在有一棵树,只有根节点,白色。

每次,可以选择一条链,选择其中一个点与树上白色的点相连接,连接后这两个点均变成黑色。

现要求选择一些链连接,使得,所有白色点到根节点的距离的第\(k\)小值最小。输出这个最小值。

树的边权均为1。

解题思路

题目过于迷幻,甚至无从下手。

首先我们先观察一些性质。比如除了根节点,其他点的度数要么是\(2\)要么是\(3\)然而并没有什么用

首先,我们肯定是取链的中间点与树上白色点相连接。

其次,考虑取怎样链。我们发现自然是越长越好。因为链越长,所谓的附着点(白色点)越多。而一个链连上一个附着点,会有所谓到根节点距离代价增1的情况,如果附着点少,可能发生多次附着的情况,这样到根节点距离代价增加的效果会积累,不利于达到最小值。

觉得没什么问题,决定试一发人品贪一波

每次选长度最长的链,附着到距离根节点最近的白色节点,维护答案。

由于是求出第\(k\)小的,我们就维护一个计数数组\(cnt[i]\),表示距离根节点距离为\(i\)的白色点有多少个。

从这个数组能找到距离根节点最近的白色点的数量。

然后当一条链附着上去时,需要区间加法。

线段树维护即可。

虽然\(k\)\(10^{9}\),但最理想的情况\(k\)\(32\)左右,所以其实实际第\(k\)个值的结果不会很大盲开了个1e5没炸

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

template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

const int up = 1e5 + 8;

class Segment_Tree{
    #define lson root << 1
    #define rson root << 1 | 1
    LL sum[up << 2];
    LL mark[up << 2];

    public:
    void build(int root, int l, int r){
        if (l == r){
            sum[root] = mark[root] = 0;
            if (l == 1) sum[root] = 1;
            return;
        }
        int mid = (l + r) >> 1;
        build(lson, l, mid);
        build(rson, mid + 1, r);
        sum[root] = sum[lson] + sum[rson];
        mark[root] = 0;
    }

    void pushdown(int root, int l, int r){
        int mid = (l + r) >> 1;
        mark[lson] += mark[root];
        sum[lson] += mark[root] * (mid - l + 1);
        mark[rson] += mark[root];
        sum[rson] += mark[root] * (r - mid);
        mark[root] = 0;
    }

    int findleft(int root, int l, int r){
        if (l == r){
            return l;
        }
        pushdown(root, l, r);
        int mid = (l + r) >> 1;
        if (sum[lson] > 0) return findleft(lson, l, mid);
        else return findleft(rson, mid + 1, r);
    }

    void update(int root, int l, int r, int ll, int rr, int val){
        if (ll <= l && r <= rr){
            mark[root] += val;
            sum[root] += val * (r - l + 1);
            return;
        }
        pushdown(root, l, r);
        int mid = (l + r) >> 1;
        if (ll <= mid) update(lson, l, mid, ll, rr, val);
        if (rr > mid) update(rson, mid + 1, r, ll, rr, val);
        sum[root] = sum[lson] + sum[rson];
    }

    int query(int root, int l, int r, LL k){
        if (l == r){
            if (sum[root] < k) return 1e9 + 8;
            return l;
        }
        pushdown(root, l, r);
        int mid = (l + r) >> 1;
        if (sum[lson] >= k) return query(lson, l, mid, k);
        else return query(rson, mid + 1, r, k - sum[lson]);
    }

}Seg;

int main(void) {
    int n;
    LL k;
    read(n);
    read(k);
    vector<int> l(n);
    for(auto & i : l) 
        read(i);
    sort(l.begin(), l.end(), greater<int>());
    Seg.build(1, 1, up);
    int ans = 1e9 + 7;
    for(auto i : l){
        int st = Seg.findleft(1, 1, up);
        Seg.update(1, 1, up, st, st, -1);
        int left = (i - 1)/ 2;
        Seg.update(1, 1, up, st + 2, st + 2 + left - 1, 1);
        Seg.update(1, 1, up, st + 2, st + 2 + i - 1 - left - 1, 1);
        int result = Seg.query(1, 1, up, k);
        ans = min(ans, result);
    }
    if (ans == (int)1e9 + 7) ans = 0;
    -- ans;
    write(ans, '\n');
    return 0;
}


posted @ 2020-12-30 13:46  ~Lanly~  阅读(176)  评论(0编辑  收藏  举报