【解题总结】SEERC 2019(Codeforces Gym 102392)

我解决的:D、I、E。

没看的:H、K。

旁观的:B、G。

看了但没做出来的:A、C、F、J。

E Life Transfer

简单题,略。

D Cycle String?

题意:给定一个长为 2 N 2N 的串,问能否将其重新排列使得新串的每一个长度为 N N 的循环子串(即这个子串可以由原串的某个后缀和前缀拼接而成)互不相同,如果能要给出一个构造。

统计每一个字符出现的数目,然后分类讨论。

  1. 只有一种字符,那必然无解。
  2. 只有两种字符,且某种字符出现了不少于 2 N 2 2N-2 次,那么就可能无解,要看 N N 是否为 1 1 或者 2 2 ,或者大于 2 2 。这里讨论一下即可。
  3. 除上面两种情况外都是有解的。设出现次数最多的字符为 a a ,如果 a a 出现了不超过 N N 次,那么只要对原字符串排序即可;否则在排序完原字符串后,要用一个除 a a 之外的字符把那一段 a a 分割为左右两部分,使得左半部分长度恰为 N N 。可以证明这一构造是合法的。

K Stranded Robot

大模拟,略。应该不是很难写…

G Projection

题意:有一个 n × m × h n \times m \times h 的三维网格,每一个网点上可能有一个方块。给定这个网格沿着两个方向投影得到的画面,大小分别为 n × m n \times m n × h n \times h ,问为了达成这样的投影,这个网格中最多要有几个方块、最少要有几个方块,并给出两种情况下方块的坐标列表。多解时输出字典序最小的那个。

n n 坐标遍历,每一层进行构造。显然,每一层的答案互不影响。

无解当且仅当这一层在 n × m n\times m 的投影中有影子,但在 n × h n\times h 的投影中没有影子,或者反过来。

如果这一层在 n × m n\times m 的投影中有 a a 个方块的影子,在 n × h n\times h 的投影中有 b b 个方块的影子,那么这一层最多可以有 a b a b 个方块,最少可以有 max ( a , b ) \max(a, b) 个方块。

对于后者要求出字典序最小的方案,只需要按照 m m 坐标或者 h h 坐标递增方向构造方块即可。

在这里插入图片描述

#include <bits/stdc++.h>
#define MAXN 105
using namespace std;
int n, m, h, a[MAXN][MAXN], b[MAXN][MAXN];
int cntm[MAXN], cnth[MAXN];
int lstm[MAXN], lsth[MAXN];
char s[MAXN];
int main(){
    scanf("%d%d%d", &n, &m, &h);
    for (int i = 1; i <= n; ++i){
        scanf("%s", s + 1);
        cntm[i] = 0;
        for (int j = 1; j <= m; ++j)
            a[i][j] = (s[j] == '1' ? 1: 0), cntm[i] += a[i][j];
    }
    for (int i = 1; i <= n; ++i){
        scanf("%s", s + 1);
        cnth[i] = 0;
        for (int j = 1; j <= h; ++j)
            b[i][j] = (s[j] == '1' ? 1: 0), cnth[i] += b[i][j];
    }
    
    // -1
    int maxi = 0, mini = 0;
    for (int i = 1; i <= n; ++i){
        if ((cntm[i] > 0 && cnth[i] == 0) || (cnth[i] > 0 && cntm[i] == 0)){
            printf("-1\n");
            return 0;
        } 
        maxi += cntm[i] * cnth[i];
        mini += max(cntm[i], cnth[i]);
    }
    
    // construct
    printf("%d\n", maxi);
    for (int i = 1; i <= n; ++i){
        for (int j = 1; j <= m; ++j)
            for (int k = 1; k <= h; ++k)
                if (a[i][j] && b[i][k]) printf("%d %d %d\n", i - 1, j - 1, k - 1);
    }
    printf("%d\n", mini);
    for (int i = 1; i <= n; ++i){
        if (!cntm[i]) continue;
        int totm = 0, toth = 0;
        for (int j = 1; j <= m; ++j)
            if (a[i][j]) lstm[++totm] = j;
        for (int j = 1; j <= h; ++j)
            if (b[i][j]) lsth[++toth] = j;
        for (int j = 1, k = 1; j <= totm; ){
            printf("%d %d %d\n", i - 1, lstm[j] - 1, lsth[k] - 1);
            if (totm - j == toth - k){
                ++j, ++k;
            } else if (totm - j > toth - k){
                ++j;
            } else {
                ++k;
            }
        }
    }
    return 0;
}

F Game on a Tree

题意:给定一个以 1 1 为根的树,一开始所有节点都是白色。A 先手将一个棋子放在某个节点,棋子会把所在节点涂黑。随后 B 和 A 轮流移动棋子,棋子不能跨过黑色节点,且只能移动到当时所在节点的祖先,或者子树中的某个节点。不能移动者输。问双方均采取最优策略时,谁赢。

这貌似是一个很经典的博弈论问题。如果将所有互相可达的点之间连一个边,得到一个新的无向图,那么结论就是:如果这个无向图的最大匹配是完美匹配,那么后手必胜;否则先手必胜。

为了求出新图的最大匹配,我们设 f ( i ) f(i) 表示 i i 号点的子树中,在最大匹配的情况下,还有多少个点没有被匹配。

i i 的所有儿子的 f f 值加起来为 t o t tot 。若其不为 0,那么 i i 就与 t o t tot 个未匹配点中的一个匹配, f ( i ) = t o t 1 f(i) = tot-1 。否则 i i 不被匹配, f ( i ) = 1 f(i) = 1

所以按上述说明 DP 一下,检查 f ( 1 ) f(1) 即可。

B Level Up

题意:有 n n 个任务,现在要尽快通过两个关卡,一开始处于第一关,通过第一关就到达第二关。通过第一、二关各要 s 1 , s 2 s_1, s_2 经验。到第二关时,原有的 s 1 s_1 经验会清零。如果通过第一关时经验超过 s 1 s_1 ,溢出的部分会继承到第二关。给定每一个任务在第一、二关时完成可获得的经验和所需时间,求最短通关时间。

我们同时考虑两个关卡,设 f ( i , j , k ) f(i, j, k) 为用前 i i 个任务,第一关达到 j j 经验,第二关达到 k k 经验时的最短时间。则转移方程容易写出。

一个坑点在于我们必须按照“在第一关时完成可获得的经验”从小到大来遍历任务,否则无法正确处理经验溢出的情况:考虑 s 1 = s 2 = 60 s_1 = s_2 = 60 ,只有两个任务 a , b a, b a a 在第一关通过获得经验 30 30 b b 90 90 ,两者在第二关通过时均不获得经验。如果 b b a a 前被枚举,那么会被判无解,反之有解。

DP 的第一维可以用滚动数组优化。

I Absolute Game

题意:A 和 B 两个人各有 N N 个数,有 N 1 N-1 轮游戏,每一轮两个人要轮流删掉自己的一个数。A 先手。假设最后 A,B 手上的数分别为 a , b a, b ,A 希望最大化两者差的绝对值,B 希望最小化两者差的绝对值。问两人均采取最优策略时,最后这个差的绝对值。

设 A 有数 { a i } \left\lbrace a_i \right\rbrace ,B 有数 { b i } \left\lbrace b_i \right\rbrace 。问题等价于 A 选一个 i i ,然后最大化 min 1 j n a i b j \min_{1 \le j \le n} |a_i - b_j|

于是就很简单了。但是最难的在于这个等价性是怎么来的呢?我不会证,不如参考官方题解

J Graph and Cycles

题意:给定一个 N N 个点的带权无向完全图,保证 N N 是奇数。定义环 e 1 , e 2 , , e k e_1, e_2, \cdots, e_k 的价格为 i = 1 k max ( w ( e i ) , w ( e i + 1 ) ) \sum_{i=1}^{k} \max(w(e_i), w(e_{i+1})) ,其中 e k + 1 = e 1 e_{k+1} = e_1 。现在要将图分割成若干个环,使得每一个环的价格之和最小。求这个价格。

由于 N N 是奇数,因此每一个点的度为偶数。可以看出,如果一个环经过点 v v ,那么一定有一条边进入 v v ,另一条走出 v v

对每一个点 v v ,其连着的边可以分成 N 1 2 \frac{N-1}{2} 组,每组属于某个环,一条进入 v v ,另一条走出 v v 。每组中权值较大的边就是这组边对价格的贡献。因此所有环的价格和就等于若干组边的价值和。

到这一步,只要恰当的分组使得价格尽量小即可。最好的分组方式就是排序后按顺序两两分组。这样就求出了答案。

但怎么说明这样转化后,一定有相对应的环分割呢?我也不会证,不如参考官方题解

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
vector<int> G[1005];
int main(){
    scanf("%d", &n);
    int u, v, w;
    while (~scanf("%d%d%d", &u, &v, &w)){
        G[u].push_back(w);
        G[v].push_back(w);
    }
    ll ans = 0;
    for (int i = 1; i <= n; ++i){
        sort(G[i].begin(), G[i].end());
        for (int j = 1; j < n - 1; j += 2)
            ans += G[i][j];
    }
    printf("%lld\n", ans);
    return 0;
}

C Find the Array

题意:交互题。有一个长为 n 250 n \le 250 的数组,里面的数互不相同,且开始时未知。有两种操作:操作 1 直接获取一个下标上的值,操作 2 接受若干下标,设为 k k 个,获得这些下标上的数两两做差的绝对值,共 k ( k 1 ) 2 \frac{k(k-1)}{2} 个(无序)。在 30 次操作内找出这个数组。

本题有一个很妙的做法。先用操作 2 输入所有下标,找出极差 d d ,其必然是两个最值的差。然后我们希望找到一个最小的 p o s pos ,使得 a a 的最值均在 [ 1 , p o s ] [1, pos] 内。这个可以二分处理。

然后针对 p o s pos ,我们希望对于所有 i p o s i \neq pos ,找出 b i = a i a p o s b_i = |a_i - a_{pos}| 。这可以用二进制分组实现:枚举从低位到高位每一个 bit,设该 bit 为 2 u 2^u ,设与该 bit 有交(即按位与不为 0)、且不等于 p o s pos 的下标集合为 B u B_u ,把 B u { p o s } B_u\cup \left\lbrace pos \right\rbrace 输入给操作 2,然后再把 B u B_u 输入给操作 2,把前者的结果除掉后者的结果,就得到了对任意 i B u i \in B_u b i b_i 构成的集合,记为 C u C_u

我们已经知道了 b b 数组的所有值(共 n 1 n-1 个),现在要把值和下标关联起来。这是二进制分组的常规操作:对于每一个值,检查其对于每一个 bit 2 u 2^u ,是否在 C u C_u 中出现。把所有出现了的 bit 或起来就是下标。

得到了 b b 数组就可以知道另一个极值在哪,设为 p o s pos' 。用两次操作 1 获得 a p o s a_{pos} a p o s a_{pos'} ,然后就可以方便地利用 b b 数组求出 a a 数组了。

总操作数不超过 1 + log n + 2 log n + 2 27 1 + \lceil \log n\rceil + 2 \lceil \log n\rceil + 2 \le 27

注意特判一些特殊情况,如操作 2 必须接受至少 2 个数。

#include <bits/stdc++.h>
using namespace std;
int n, a[255], b[255], lst[255], tot;
unordered_multiset<int> st[13];
unordered_set<int> sttot;
int getmaxi(int l){
    int maxi = 0;
    l = l * (l - 1) >> 1;
    for (int i = 1, t; i <= l; ++i){
        scanf("%d", &t);
        maxi = max(maxi, t);
    }
    return maxi;
}
int main(){
    scanf("%d", &n);
    // find the maximum differece
    if (n == 1){
        printf("1 1\n");
        fflush(stdout);
        scanf("%d", &a[1]);
        printf("3 %d\n", a[1]);
        return 0;    
    }

    printf("2 %d", n);
    for (int i = 1; i <= n; ++i)
        printf(" %d", i);
    putchar('\n');
    fflush(stdout);
    int maxi = getmaxi(n);

    // find the base position
    int l = 2, r = n;
    while (r > l){
        int mid = (l + r) >> 1;
        printf("2 %d", mid);
        for (int i = 1; i <= mid; ++i)
            printf(" %d", i);
        putchar('\n');
        fflush(stdout);
        if (getmaxi(mid) == maxi) r = mid;
        else l = mid + 1;
    }
    // here, pos is l
    
    // group the number by bits
    for (int i = 1, logg = 0; i <= n; i <<= 1, ++logg){
        tot = 0;
        for (int j = 1; j <= n; ++j){
            if (!(i & j) || j == l) continue;
            lst[++tot] = j;
        }
        if (tot == 0) continue;
        
        printf("2 %d %d", tot + 1, l);
        for (int j = 1; j <= tot; ++j)
            printf(" %d", lst[j]);
        putchar('\n');
        fflush(stdout);
        for (int j = 1, t; j <= (tot + 1) * tot / 2; ++j){
            scanf("%d", &t);
            st[logg].insert(t);
        }

        if (tot > 1){
            printf("2 %d", tot);
            for (int j = 1; j <= tot; ++j)
                printf(" %d", lst[j]);
            putchar('\n');
            fflush(stdout);
            for (int j = 1, t; j <= (tot - 1) * tot / 2; ++j){
                scanf("%d", &t);
                st[logg].erase(st[logg].find(t));
            }
        }
        
        for (int x: st[logg])
            sttot.insert(x);
    }

    // find array b
    int logg = 0, p = -1;
    while ((1 << logg) <= n) ++logg;
    for (int x: sttot){
        int pos = 0;
        for (int i = 0; i < logg; ++i){
            if (st[i].count(x)) pos |= (1 << i);
        }
        b[pos] = x;
        if (x == maxi) p = pos;
    }
    // here, pos' is p

    printf("1 %d\n", l);
    fflush(stdout);
    scanf("%d", &a[l]);
    printf("1 %d\n", p);
    fflush(stdout);
    scanf("%d", &a[p]);

    // calc the answer
    int sgn = (a[l] < a[p] ? 1: -1);
    printf("3");
    for (int i = 1; i <= n; ++i){
        if (i != l) a[i] = a[l] + sgn * b[i];
        printf(" %d", a[i]);
    }
    putchar('\n');
    fflush(stdout);
    return 0;
}

A Max or Min

待补。。。

H Tree Permutations

待补。。。

猜你喜欢

转载自blog.csdn.net/zqy1018/article/details/108265324