某岛

… : "…アッカリ~ン . .. . " .. .
October 21, 2021

Facebook Hacker Cup 2021 Round 1

A1.
给定一个 ’01-‘ 组成的字符串 s,设 f(s) 为忽略掉 s 中所有 ‘-‘ 字符后,相邻字符发生变换的次数。
求 f(s)。
扫一遍即可。

A2
统计所有 s 的子串的 f 值。
统计相邻的 ’01’ pair 的贡献即可。

const int N = int(1e6) + 9;
char s[N];
int n;

int main() {

#ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
#endif

    Rush {
        RD(n); RS(s);
        Int z = 0;
        char lc = '?'; // 上一个字符
        int lp = 0; // 上一个字符的位置

        REP(i, n) {
            char c = s[i];
            if(c != 'F') {
                if (lc != c) {
                    if(lc != '?') z += Int(lp) * (n-i);
                    lc = c;
                }
                lp = i+1;
            }
        }
        OT(z);
    }
}

A3
s 中还包含 ‘.’ 字符,如果出现 ‘.’ 字符,则用此前的字符串替换 ‘.’ 位置,询问 A2。

延续 A2 的思路,我们需要对每个 pair 统计出左边和右边分别有多少字符。
这个比较简单,但是每个 ‘.’ 内部还会出现很多 pair,我们需要对这些内部的 pair 也 O(1) 时间统计。
这样首先我们需要改进 A2 的算法,再开一个 ls 变量记录前面所有 pair 的贡献,这样就可以不支持后续长度的情况下 incremental 的维护答案。

const int N = int(1e6) + 9;
char s[N];
int n;

int main() {

#ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
#endif

    Rush {
        RD(n); RS(s);
        Int z = 0;
        char lc = '?'; // 上一个字符
        int lp = 0; // 上一个字符的位置
        Int ls = 0; // 右边新增加一个字符时答案的增量。

        REP(i, n) {
            z += ls;
            char c = s[i];
            if (c != 'F') {
                if (lc != '?' && lc != c) {
                    z += lp;
                    ls += lp;
                }
                lp = i+1;
                lc = c;
            }
        }
        OT(z);
    }
}

进一步考虑 ‘.’ 的影响,我们不仅需要知道当右边新增加一个字符时的增量 ls,还需要对称的,知道当左边新增加一个字符时的增量 rs,
那么新的答案就是 2z + len(ls+rs),还要考虑合并的位置会不会产生一组新的 pair。
因此还需要维护左右第一个字符是什么和所在位置以及一共有多少 pair。各种维护即可。

const int N = int(1e6) + 9;
char s[N];
int n;

int main() {

#ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
#endif

    Rush {
        RD(n); RS(s);
        Int z = 0;
        char lc = '?', rc = '?'; //
        Int lp = 0, rp = 0, ct = 0;
        Int ls = 0, rs = 0;
        Int len = 0;

        REP(i, n) {
            char c = s[i];
            if (c == '.') {

                z = z*2 + len*(ls+rs);
                ls = ls*2 + len*ct;
                rs = rs*2 + len*ct;
                ct *= 2;

                if (lc != rc) {
                    ct += 1; ls += lp; rs += (len-rp+1);
                    z += lp * (len-rp+1);
                }
                if (lc != '?') lp += len;
                len *= 2;

            } else {

                z += ls;
                len += 1;

                if (c != 'F') {

                    if (rc == '?') {
                        rc = c, rp = len;
                    }

                    if (lc != '?' && lc != c) {
                        z += lp;
                        ls += lp;
                        ct += 1;
                    }
                    lp = len;
                    lc = c;
                }

                rs += ct;
            }
        }
        OT(z);
    }
}

C.
感觉是树形 dp 基本功题,最简单的方法应该是 dfs() 两次,
一次得到子树的 dp 状态,一次得到子树外的 dp 状态,但是比赛时思路比较混乱。

赛后读了一下 nhb 的代码,发现忽略了一个重要得条件,就是边权 <= 20。。。
然后就可以直接暴力加维了。。囧。

当然边分治也可以,参考 neal wu 的代码

不知道没有边权的限制还能不能做。