Featured image of post Codeforces Round #1098(Div.2)

Codeforces Round #1098(Div.2)

这场有点大份,被喷惨了,谁家C1C2出这个。

B

题目大意:Alice跟Bob正在玩追逐游戏,Alice想要抓住Bob,游戏地点是一个长度为 $n$ 的环,初始时Alice位于位置 $a$ ,Bob位于位置 $b$ ,每秒钟,Bob可以移动到相邻的位置,也可以原地不动,在整个游戏过程中,Bob最多可以移动 $k$ 次,观察到Bob的动作后,Alice可以移动到相邻的位置,也可以原地不动,如果此时两人在同一个位置,那么就抓住了。

现在双方都采取最优策略,问Bob最晚多少秒内能被抓住?

数据范围:$2 \leq n \leq 10^8$。

思路:首先特判一下 $n \leq 3$ 的情况,这点很多人被坑了。

随后,模拟可知,两个人无非是沿最短路追,等到Bob无法移动了,就只有Alice动了,于是得到答案。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void solve() {
    ll n, a, b, k;
    cin >> n >> a >> b >> k;
    if (n <= 3) {
        cout << 1 << endl;
        return;
    }
    ll tem = abs(b - a);
    ll tem2 = n - tem;
    cout << min(tem, tem2) + k << endl;
    return;
}

C1

题目大意:给定一个非负整数 $a$ 和一个长度为 $n$ 的非空严格递增数字序列 $d$ ,其中 $0 \leq d_i \leq 9$ ,请求出仅由 $d$ 中数字组成的非负整数 $b$ 中,使得 $|a-b|$ 最小的值。

注意:在本题中, $n=2$ 。

数据范围:$0 \leq a \leq 10^{17},0 \leq d_i \leq 9$。

思路:跟C2同一份代码,建议直接左转C2思路。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
void solve() {
    ll n, a;
    cin >> a >> n;
    string s = to_string(a);
    int m = sz(s);
    vl d(n);
    rep(i, 0, n - 1) cin >> d[i];
    ranges::sort(d);
    ll ans = LLONG_MAX;
    vl tem(m + 1, 1);
    rep(i, 1, m) tem[i] = tem[i - 1] * 10;
    if (m > 1) {
        ll tem2 = 0;
        rep(i, 1, m - 1) tem2 = tem2 * 10 + d[n - 1];
        ans = min(ans, abs(tem2 - a));
    }
    int idx = -1;
    rep(i, 0, n - 1) {
        if (d[i] != 0) {
            idx = i;
            break;
        }
    }
    if (idx != -1) {
        ll tem2 = d[idx];
        rep(i, 2, m + 1) {
            if (tem2 > (LLONG_MAX - d[0]) / 10) {
                tem2 = LLONG_MAX;
                break;
            }
            tem2 = tem2 * 10 + d[0];
        }
        if (tem2 != LLONG_MAX) ans = min(ans, abs(tem2 - a));
    }
    vector<array<int, 3>> vis(m + 1);
    vector<array<ll, 3>> ma(m + 1);
    auto dfs = [&](this auto&& dfs, int pos, int st) -> ll {
        if (pos == m) return 0;
        if (vis[pos][st]) return ma[pos][st];
        vis[pos][st] = 1;
        ll res = LLONG_MAX;
        int tem2 = s[pos] - '0';
        rep(i, 0, n - 1) {
            if (pos == 0 && m > 1 && d[i] == 0) continue;
            ll temp = 0;
            int tx = st;
            if (st == 0) {
                if (d[i] < tem2) {
                    tx = 1;
                    temp += (tem2 - d[i]) * tem[m - pos - 1];
                } else if (d[i] > tem2) {
                    tx = 2;
                    temp += (d[i] - tem2) * tem[m - pos - 1];
                }
            } else if (st == 1) {
                temp += (tem2 - d[i]) * tem[m - pos - 1];
            } else {
                temp += (d[i] - tem2) * tem[m - pos - 1];
            }
            temp += dfs(pos + 1, tx);
            res = min(res, temp);
        }
        ma[pos][st] = res;
        return res;
    };
    cout << min(ans, dfs(0, 0)) << endl;
    return;
}

C2

题目大意:给定一个非负整数 $a$ 和一个长度为 $n$ 的非空严格递增数字序列 $d$ ,其中 $0 \leq d_i \leq 9$ ,请求出仅由 $d$ 中数字组成的非负整数 $b$ 中,使得 $|a-b|$ 最小的值。

数据范围:$0 \leq a \leq 10^{17},0 \leq d_i \leq 9$。

思路:赛时联想到之前的这道题R1077-D ,然后就是只要考虑当前比 $a$ 少一位的情况,高一位的情况,而同一位的情况可以从高位到低位做数位DP,状态定义为当前是否大于、等于或者小于当前的数,即可(其实可以直接从那题拉板子)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
void solve() {
    ll n, a;
    cin >> a >> n;
    string s = to_string(a);
    int m = sz(s);
    vl d(n);
    rep(i, 0, n - 1) cin >> d[i];
    ranges::sort(d);
    ll ans = LLONG_MAX;
    vl tem(m + 1, 1);
    rep(i, 1, m) tem[i] = tem[i - 1] * 10;
    if (m > 1) {
        ll tem2 = 0;
        rep(i, 1, m - 1) tem2 = tem2 * 10 + d[n - 1];
        ans = min(ans, abs(tem2 - a));
    }
    int idx = -1;
    rep(i, 0, n - 1) {
        if (d[i] != 0) {
            idx = i;
            break;
        }
    }
    if (idx != -1) {
        ll tem2 = d[idx];
        rep(i, 2, m + 1) {
            if (tem2 > (LLONG_MAX - d[0]) / 10) {
                tem2 = LLONG_MAX;
                break;
            }
            tem2 = tem2 * 10 + d[0];
        }
        if (tem2 != LLONG_MAX) ans = min(ans, abs(tem2 - a));
    }
    vector<array<int, 3>> vis(m + 1);
    vector<array<ll, 3>> ma(m + 1);
    auto dfs = [&](this auto&& dfs, int pos, int st) -> ll {
        if (pos == m) return 0;
        if (vis[pos][st]) return ma[pos][st];
        vis[pos][st] = 1;
        ll res = LLONG_MAX;
        int tem2 = s[pos] - '0';
        rep(i, 0, n - 1) {
            if (pos == 0 && m > 1 && d[i] == 0) continue;
            ll temp = 0;
            int tx = st;
            if (st == 0) {
                if (d[i] < tem2) {
                    tx = 1;
                    temp += (tem2 - d[i]) * tem[m - pos - 1];
                } else if (d[i] > tem2) {
                    tx = 2;
                    temp += (d[i] - tem2) * tem[m - pos - 1];
                }
            } else if (st == 1) {
                temp += (tem2 - d[i]) * tem[m - pos - 1];
            } else {
                temp += (d[i] - tem2) * tem[m - pos - 1];
            }
            temp += dfs(pos + 1, tx);
            res = min(res, temp);
        }
        ma[pos][st] = res;
        return res;
    };
    cout << min(ans, dfs(0, 0)) << endl;
    return;
}

D

题目大意:平面上有 $n$ 个不同的整点,其中第 $i$ 个点位于 $(x_i,y_i)$ 处,现在要给这些点着色,选择两个整数 $k_1,k_2$ , $x \leq k_1, y > k_2$ 的被染成红色, $x > k_1, y > k_2$ 的被染成绿色, $x \leq k_1, y \leq k_2$ 的被染成蓝色, $x > k_1, y \leq k_2$ 的被染成黄色,现在问:有多少种不同的染色方式?

数据范围:$4 \leq n \leq 2 \cdot 10^6,1 \leq x_i,y_i \leq n$。

思路:这题的这个读入数据被骂惨了,oier净想着卡常。

很显然会想到对 $x$ 排序,使用类似哈希表(但是2e6会被卡,所以这里依旧采用我们熟悉的数组滑窗套路),考虑在 $x_i,x_{i+1}$ 之间画出一条竖线,此时想到,如何才能画出一条竖线?

画图很显然地可以看出,上限是左半部分的上限跟右半部分的上限的最小值,下限是左半部分的下限跟右半部分的下限的最大值,于是预先把 $y$ 都收集起来,二分查找就可以得到答案了,这里可以用前后缀处理,跑得快一点。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void solve() {
    int n;
    cin >> n;
    vector<pll> ma(n);
    rep(i, 0, n - 1) cin >> ma[i].first >> ma[i].second;
    sort(all(ma), [&](const pll& x, const pll& y) {
        if (x.first == y.first) return x.second < y.second;
        return x.first < y.first;
    });
    vl tem;
    rep(i, 0, n - 1) { tem.push_back(ma[i].second); }
    ranges::sort(tem);
    tem.erase(unique(all(tem)), tem.end());
    ll ans = 0;
    ll maxx = LLONG_MIN, mixx = LLONG_MAX;
    vl suf1(n, LLONG_MAX), suf2(n, LLONG_MIN);
    suf1[n - 1] = ma[n - 1].second, suf2[n - 1] = ma[n - 1].second;
    frep(i, n - 2, 0) {
        suf1[i] = min(suf1[i + 1], ma[i].second);
        suf2[i] = max(suf2[i + 1], ma[i].second);
    }
    rep(i, 0, n - 2) {
        maxx = max(maxx, ma[i].second);
        mixx = min(mixx, ma[i].second);
        if (ma[i].first == ma[i + 1].first) continue;
        ll tem2 = max(mixx, suf1[i + 1]);
        ll tem3 = min(maxx, suf2[i + 1]);
        ll cnt = ranges::lower_bound(tem, tem3) - ranges::lower_bound(tem, tem2);
        if (cnt >= 0) ans += cnt;
    }
    cout << ans << endl;
    return;
}

E1

题目大意:给你一个区间,里面的 $-1$ 是待填空位,用非负整数填充,使得整个区间总和恰好等于 $m$ ,对每种合法填法,计算每个前缀和的平方和,然后把所有填法的结果加起来取模。

注意本题不带修,操作是诈骗的,E2是带修的。

数据范围:$1 \leq n \leq 3 \cdot 10^5,-1 \leq a_i \leq 10^6$。

思路:很显然地,我们会联想到隔板法,同时维护普通前缀和,跟当前的未知个数,然后很自然地进入推式子环节。

不妨设 $pre_i$ 为已知的前缀和, $x_i$ 为当前未知位置的前缀和贡献,于是有

$$ \sum_{\text{合法填法}}\sum_{i=l}^{r}(pre_i+x_i)^2 $$

展开平方可得:

$$ (pre_i+x_i)^2=pre_i^2+2pre_ix_i+x_i^2 $$

所以答案可以分成三部分分别计算。

令当前区间中未知位置个数为 $k$ ,待填入未知位置的总和为 $N$ ,也就是代码中的 $N=m-pr$ ,若 $N < 0$ 则无解。由于每个未知数都是非负整数,所以合法方案数可以写成生成函数取系数:

$$ [z^N]\left(\frac{1}{1-z}\right)^k=\binom{N+k-1}{k-1} $$

记这个值为 $cnt0$ 。

于是第一部分就是:

$$ \sum_{\text{合法填法}}pre_i^2=cnt0 \cdot pre_i^2 $$

接下来考虑 $\sum x_i$ 。对于一个固定前缀 $i$ ,如果这个前缀中有 $c_i$ 个未知位置,那么 $x_i$ 就是这 $c_i$ 个未知数的和。先考虑其中某一个未知数 $y$ 的总贡献,它对应的带权生成函数为:

$$ \left(\sum_{y\geq 0}yz^y\right)\left(\frac{1}{1-z}\right)^{k-1} =\frac{z}{(1-z)^{k+1}} $$

于是:

$$ \sum_{\text{合法填法}}y=[z^N]\frac{z}{(1-z)^{k+1}}=\binom{N+k-1}{k} $$

记这个值为 $cnt1$ ,那么:

$$ \sum_{\text{合法填法}}x_i=c_i \cdot cnt1 $$

最后考虑 $\sum x_i^2$ 。设前缀中的未知数分别为 $y_1,y_2,\cdots,y_{c_i}$ ,则有:

$$ x_i^2=\sum_{j=1}^{c_i}y_j^2+2\sum_{1 \leq j先考虑单个未知数的平方项,它的生成函数为:

$$ \left(\sum_{y\geq 0}y^2z^y\right)\left(\frac{1}{1-z}\right)^{k-1} =\frac{z(1+z)}{(1-z)^{k+2}} $$

所以:

$$ \sum_{\text{合法填法}}y_j^2 =[z^N]\frac{z(1+z)}{(1-z)^{k+2}} =\binom{N+k}{k+1}+\binom{N+k-1}{k+1} $$

又因为:

$$ \binom{N+k}{k+1}=\binom{N+k-1}{k}+\binom{N+k-1}{k+1} $$

记:

$$ cnt2=\binom{N+k-1}{k+1} $$

就有:

$$ \sum_{\text{合法填法}}y_j^2=cnt1+2cnt2 $$

再考虑两个不同未知数的乘积项:

$$ \left(\sum_{y_j\geq 0}y_jz^{y_j}\right)\left(\sum_{y_q\geq 0}y_qz^{y_q}\right)\left(\frac{1}{1-z}\right)^{k-2} =\frac{z^2}{(1-z)^{k+2}} $$

所以:

$$ \sum_{\text{合法填法}}y_jy_q=[z^N]\frac{z^2}{(1-z)^{k+2}}=\binom{N+k-1}{k+1}=cnt2 $$

代回 $x_i^2$ ,就能得到:

$$ \sum_{\text{合法填法}}x_i^2 =c_i(cnt1+2cnt2)+2\binom{c_i}{2}cnt2 =c_i \cdot cnt1+c_i(c_i+1)cnt2 $$

因此,对于每个前缀 $i$ ,贡献为:

$$ cnt0 \cdot pre_i^2+2pre_i(c_i \cdot cnt1)+c_i \cdot cnt1+c_i(c_i+1)cnt2 $$

这也就对应代码里的:

$$ cnt0=\binom{N+k-1}{k-1},\quad cnt1=\binom{N+k-1}{k},\quad cnt2=\binom{N+k-1}{k+1} $$

以及每次枚举前缀时维护的 $pre_i$ 和 $c_i$ 。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
const ll MOD = 998244353;
constexpr int MX = 1e6 + 1;
ll F[MX];      // 预处理阶乘
ll INV_F[MX];  // 预处理逆元
ll qpow(ll x, int n) {
    ll res = 1;
    for (; n; n >>= 1) {
        if (n % 2) res = res * x % MOD;
        x = x * x % MOD;
    }
    return res;
}
auto init = [] {
    F[0] = 1;
    for (int i = 1; i < MX; i++) F[i] = F[i - 1] * i % MOD;  // 预处理阶乘
    INV_F[MX - 1] = qpow(F[MX - 1], MOD - 2);
    for (int i = MX - 1; i; i--) {
        INV_F[i - 1] = INV_F[i] * i % MOD;
    }  // 预处理逆元
    return 0;
}();
// 计算C(n,m),即从n个数中取m个数
ll comb(int n, int m) { return m < 0 || m > n ? 0 : F[n] * INV_F[m] % MOD * INV_F[n - m] % MOD; }
void solve() {
    ll n, q, op, l, r, m;
    cin >> n >> q;
    vl a(n + 1);
    rep(i, 1, n) cin >> a[i];
    vl cnt(n + 1);
    vl pre(n + 1);
    rep(i, 1, n) {
        pre[i] = pre[i - 1];
        cnt[i] = cnt[i - 1];
        if (a[i] == -1)
            cnt[i]++;
        else
            pre[i] = (pre[i - 1] + a[i]);
    }
    rep(v, 0, q - 1) {
        cin >> op >> l >> r >> m;
        ll tem = cnt[r] - cnt[l - 1];
        ll pr = pre[r] - pre[l - 1];
        ll ans = 0;
        if (pr > m) {
            cout << 0 << endl;
            return;
        }
        if (tem == 0) {
            if (pr != m) {
                cout << 0 << endl;
                return;
            }
            ll tem2 = 0;
            rep(i, l, r) {
                tem2 = (tem2 + a[i]) % MOD;
                ans = (ans + tem2 * tem2 % MOD) % MOD;
            }
            cout << ans << endl;
            return;
        }
        ll tem2 = 0;
        ll tem3 = 0;
        ll cnt0 = comb(m - pr + tem - 1, tem - 1);
        ll cnt1 = comb(m - pr + tem - 1, tem);
        ll cnt2 = comb(m - pr + tem - 1, tem + 1);
        rep(i, l, r) {
            if (a[i] == -1)
                tem3++;
            else
                tem2 = (tem2 + a[i]) % MOD;
            ll tem4 = tem3 * cnt1 % MOD;
            ll tem5 = (tem4 + tem3 * (tem3 + 1) % MOD * cnt2 % MOD) % MOD;
            ans = (ans + cnt0 * tem2 % MOD * tem2 % MOD) % MOD;
            ans = (ans + 2 * tem2 % MOD * tem4 % MOD + tem5) % MOD;
        }
        cout << ans << endl;
    }
    return;
}