码迷,mamicode.com
首页 > 其他好文 > 详细

Educational Codeforces Round 82 (Rated for Div. 2)

时间:2020-02-13 09:57:26      阅读:73      评论:0      收藏:0      [点我收藏+]

标签:基于   n+1   top   部分   设计   erase   round   else   memset   

题目链接:https://codeforces.com/contest/1303

A - Erasing Zeroes

水题

B - National Project

题意:修一条长度为n的路,要求至少一半是good的。注意在每[1,g]天可以修good的路,然后每[g+1,g+b]天可以修bad的路,依次交替。问按要求修完整条路需要的最短时间。

题解:看了一下数据量可以设计一个很显然的二分算法,二分枚举最短时间x,假如一开始设置了二分的下界是n,那么假如不管good和bad总是可以把路修完的,这时就只需要看这x天里面是不是有至少m=(n+1)/2个good即可。不太清楚O(1)是怎么做的,根本不差这点复杂度,大不了确定一下二分的上下界就可以了。

一个更好的下界,应该是max(n,(m/g-1)(g+b)),更好的上界应该是(n/g+1)(g+b),不过这样也就多缩减了一点点,没啥意义。

C - Perfect Keyboard

题意:构造一个26个字母各出现1次的序列,使得题目给的字符串中,相邻两个字符在序列中也是相邻的。

题解:首先假如有某个字符有多于2种邻居,就直接NO,其次假如成环也是直接NO,否则一定会恰好有两个只有1个邻居的点。相当于就是构造一条欧拉路。简洁的写法是直接用set作为“邻接链表”,然后判断是否是欧拉路,是则直接(从度为1的某个点开始)dfs输出,然后把没有被dfs经过的点再补一下。

char s[205];
set<int> G[128];
 
bool vis[128];
 
void dfs(int u, int p) {
    if(vis[u] == 1)
        return;
    putchar(u);
    vis[u] = 1;
    for(auto &v : G[u]) {
        if(v == p)
            continue;
        dfs(v, u);
    }
}
 
void test_case() {
    scanf("%s", s + 1);
    int n = strlen(s + 1);
    if(n == 1) {
        puts("YES");
        puts("abcdefghijklmnopqrstuvwxyz");
        return;
    }
    for(int c = 'a'; c <= 'z'; ++c) {
        G[c].clear();
        vis[c] = 0;
    }
    for(int i = 2; i <= n; ++i) {
        G[s[i]].insert(s[i - 1]);
        G[s[i - 1]].insert(s[i]);
    }
    int cnt1 = 0, head = -1;
    for(int c = 'a'; c <= 'z'; ++c) {
        if(G[c].size() >= 3) {
            puts("NO");
            return;
        }
        if(G[c].size() == 1) {
            ++cnt1;
            head = c;
        }
    }
    if(cnt1 != 2) {
        puts("NO");
        return;
    }
    puts("YES");
    dfs(head, -1);
    for(int c = 'a'; c <= 'z'; ++c)
        dfs(c, -1);
    putchar('\n');
    return;
}

当然比赛时想的就比较蠢了,直接往字母的两边放,这样导致很多条件判断。

char s[200005];
char t[105];
char l[256], r[256];
bool vis[256][256];
 
void test_case() {
    scanf("%s", s + 1);
    //puts(s + 1);
    int n = strlen(s + 1);
    if(n <= 3) {
        puts("YES");
        if(s[1] == s[3])
            n = 2;
        if(n == 1) {
            puts("xzytabcdefghijklmnopqrsuvw");
            return;
        }
        if(n == 2) {
            putchar(s[1]);
            putchar(s[2]);
            for(int i = 'a'; i <= 'z'; ++i) {
                if(i == s[1] || i == s[2])
                    continue;
                putchar(i);
            }
            putchar('\n');
            return;
        }
        assert(n == 3);
        putchar(s[1]);
        putchar(s[2]);
        putchar(s[3]);
        for(int i = 'a'; i <= 'z'; ++i) {
            if(i == s[1] || i == s[2] || i == s[3])
                continue;
            putchar(i);
        }
        putchar('\n');
        return;
    }
    assert(n > 3);
    for(int i = 'a'; i <= 'z'; ++i) {
        l[i] = -1;
        r[i] = -1;
        for(int j = 'a'; j <= 'z'; ++j)
            vis[i][j] = 0;
    }
    for(int i = 2; i <= n; ++i) {
        vis[s[i]][s[i - 1]] = 1;
        vis[s[i - 1]][s[i]] = 1;
    }
    int cnt1 = 0, lmost = -1, rmost = -1;
    for(int i = 'a'; i <= 'z'; ++i) {
        int cnt = 0;
        for(int j = 'a'; j <= 'z'; ++j)
            cnt += vis[i][j];
        if(cnt > 2) {
            puts("NO");
            return;
        }
        if(cnt == 1) {
            ++cnt1;
            if(lmost == -1)
                lmost = i;
            else
                rmost = i;
        }
    }
    if(cnt1 != 2) {
        puts("NO");
        return;
    }
 
    for(int i = 2; i <= n; ++i) {
        if(l[s[i - 1]] == s[i] || r[s[i - 1]] == s[i])
            continue;
        if(r[s[i - 1]] == -1 && l[s[i]] == -1) {
            r[s[i - 1]] = s[i];
            l[s[i]] = s[i - 1];
            continue;
        }
        if(l[s[i - 1]] == -1 && r[s[i]] == -1) {
            l[s[i - 1]] = s[i];
            r[s[i]] = s[i - 1];
            continue;
        }
        puts("NO");
        return;
    }
 
    if(r[lmost] == -1)
        swap(lmost, rmost);
 
    puts("YES");
    memset(t, 0, sizeof(t));
    int top = 0;
    int cur = lmost;
    while(cur != -1) {
        t[top++] = cur;
        cur = r[cur];
    }
    printf("%s", t);
    for(int i = 'a'; i <= 'z'; ++i) {
        int out = 1;
        for(int j = 0; j < top; ++j) {
            if(t[j] == i) {
                out = 0;
                break;
            }
        }
        if(out)
            putchar(i);
    }
    putchar('\n');
    return;
}

感觉应该会有人挂n=1的case。

*D - Fill The Bag

题意:有一个容量为x<=1e18的Bag,然后有n个大小为2的非负幂的物品,每次操作可以把一个物品分成等大的两半,求最少的操作把Bag填满。

题解:直接贪心,显然是从x的低位开始构造,先把更低位的物品全部加起来,那么这个范围内的数字都是可以表示,需要的话就直接扣除,毕竟“合并两个等大的物品”是没有花费的。然后假如某位没办法构造的话,就要一直往高位找到第一个有值的,然后一直分两半下来。注意其实这里复杂度应该是一个log的,不过反正两个log也没什么影响。

int cnt[40];
 
void test_case() {
    ll x;
    int n;
    scanf("%lld%d", &x, &n);
    for(int i = 0; i < 32; ++i)
        cnt[i] = 0;
    ll sum = 0;
    for(int i = 1, ai; i <= n; ++i) {
        scanf("%d", &ai);
        sum += ai;
        int k = 0;
        while(ai > 1) {
            ++k;
            ai >>= 1;
        }
        ++cnt[k];
    }
    if(sum < x) {
        puts("-1");
        return;
    }
    int ans = 0;
    sum = 0;
    for(int k = 0; k < 32; ++k) {
        sum += 1ll * cnt[k] * (1ll << k);
        cnt[k] = 0;
        if((x >> k) & 1) {
            if(sum >= (1ll << k)) {
                sum -= (1ll << k);
                continue;
            }
            while(cnt[k] == 0) {
                for(int k1 = k + 1; k1 < 32; ++k1) {
                    if(cnt[k1]) {
                        ++ans;
                        --cnt[k1];
                        cnt[k1 - 1] += 2;
                        break;
                    }
                }
            }
            sum += 1ll * cnt[k] * (1ll << k);
            cnt[k] = 0;
            assert(sum >= (1ll << k));
            sum -= (1ll << k);
            continue;
        }
    }
    printf("%d\n", ans);
}

注意:
1、左移运算符也可能会导致int溢出,因为x是ll范围的。
2、提交前消除所有的编译器的warning,scanf的lld的warning其实也可以避免,自己写一个返回ll的快读,也不会浪费多少资源。

ll read() {
    ll x = 0, f = 1;
    char c = getchar();
    while(!isdigit(c)) {
        if(c == '-')
            f = -1;
        c = getchar();
    }
    while(isdigit(c)) {
        x = x * 10 + (c - '0');
        c = getchar();
    }
    return x * f;
}

*E - Erase Subsequences

题意:从一个长度为 \(n(1\leq n \leq 400)\) 的字符串 \(s\) 中,进行至多两次操作,问是否能构造出长度为 \(m(1\leq m\leq 400)\) 的字符串 \(t\) 。每次操作,从字符串 \(s\) 中取出一个子序列,接在当前字符串 \(p\) 的后面,初始时, \(p\) 为空串。同一个字符不能被取出2次。

题解:一开始搞了个贪心,结果连第一个样例都过不了,其实看复杂度就知道过不了了。看一下性质(无后效性)其实很容易想到一种 \(dp\) 的算法,但是只想到一个 \(O(n^4)\) 的dp,先枚举字符串 \(t\) 的前半部分和后半部分的分界线,然后用一个 \(dp[i][j][k]\) 表示字符串 \(s\) 的前 \(i\) 个字符是否可以匹配前半的字符串 \(t\) 的前 \(j\) 个字符以及字符串 \(t\) 的后半的前 \(k\) 个字符。这个算法不高效的原因很可能是因为lyd说的“没有把状态压缩到极致”,很明显每个 \(dp\) 只是布尔值的话可以用一些什么bitset优化。别人的正解其实多压缩了一维,也改变了实际上设计的状态,用 \(dp[i][j]\) 表示字符串 \(s\) 的前 \(i\) 个字符匹配字符串 \(t\) 的前半的前 \(j\) 个字符时,最多匹配字符串 \(t\) 的后半的前多少个字符。进行这样的状态压缩,实际上基于一种贪心,因为匹配 \(k\) 个字符可行,那么匹配少于 \(k\) 个字符显然都是可行的。

int n, m;
char s[405], t[805];
int dp[405][805];

bool check(int d) {
    for(int i = 0; i <= n; ++i) {
        for(int j = 0; j <= m; ++j)
            dp[i][j] = -INF;
    }
    dp[0][0] = 0;
    for(int i = 1; i <= n; ++i) {
        for(int j = 0; j <= d; ++j) {
            if(dp[i - 1][j] == m - d)
                dp[i][j] = m - d;
            else {
                if(dp[i - 1][j] >= 0)
                    dp[i][j] = dp[i - 1][j] + (s[i] == t[d + 1 + dp[i - 1][j]]);
            }
        }
        for(int j = 1; j <= d; ++j) {
            if(s[i] == t[j])
                dp[i][j] = max(dp[i][j], dp[i - 1][j - 1]);
        }
//        for(int j = 0; j <= d; ++j)
//            printf("dp[%d][%d]=%d\n", i, j, dp[i][j]);
    }
//    if(dp[n][d] == m - d)
//        printf("d=%d\n", d);
    return (dp[n][d] == m - d);
}

void test_case() {
    scanf("%s%s", s + 1, t + 1);
    n = strlen(s + 1), m = strlen(t + 1);
    for(int i = 1, j = 1; i <= n; ++i) {
        if(s[i] == t[j]) {
            ++j;
            if(j > m) {
                puts("YES");
                return;
            }
        }
    }
    for(int d = 1; d < m; ++d) {
        if(check(d)) {
            puts("YES");
            return;
        }
    }
    puts("NO");
    return;
}

收获:
1、dp时不合法状态最好设为正负无穷这种越界值。
2、在进行下标访问的时候要先判断合法性。
3、对于设计出来的状态是布尔值的dp,假如总是有一半是0另一半是1。这种可以用一个整数来压缩。

Educational Codeforces Round 82 (Rated for Div. 2)

标签:基于   n+1   top   部分   设计   erase   round   else   memset   

原文地址:https://www.cnblogs.com/KisekiPurin2019/p/12302160.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!