Atcoder Regular Contest 169 A~E

A.Please Sign(图论)

题意:

给定长度为 n n n的数组 a a a,长度为 n − 1 n-1 n1的数组 p p p,现在执行无数次以下操作:

  • 对于 2 ≤ i ≤ n 2 \le i \le n 2in ,按顺序执行 a p i = a p i + a i a_{p_i}=a_{p_i}+a_i api=api+ai

确定最后 a 1 a_1 a1是正数还是负数还是 0 0 0

分析:

考虑所有 i i i p i p_i pi建边,发现是一棵以 1 1 1为根的树,再计算每个节点距离 1 1 1的深度,同一深度的权重相同,因此需将每个深度的节点的权值和都相加,如果权值和为 0 0 0 ,说明这一深度层无用,否则最终答案的正负取决于最深深度节点的权值和。

代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int N = 2.5e5 + 5;
const int inf = 0x3f3f3f3f;
LL n, a[N], p[N], f[N], dep[N], mx;
vector<int> e[N];

void dfs(int u) {
    mx = max(mx, dep[u]);
    for (auto v: e[u]) {
        dep[v] = dep[u] + 1;
        dfs(v);
    }
}

int main() {
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    for (int i = 2; i <= n; i++)
        cin >> p[i], e[p[i]].push_back(i);
    dfs(1);
    for (int i = 2; i <= n; i++)
        f[dep[i]] += a[i];
    int flag = 0;
    for (int i = n; i; i--)
        if (f[i]) {
            flag = f[i] > 0 ? 1 : -1;
            break;
        }
    a[1] = flag * inf + a[1];
    if (a[1] > 0)
        cout << "+";
    else if (a[1] == 0)
        cout << "0";
    else
        cout << "-";
    return 0;
}

B.Subsegments with Small Sums(思维)

题意:

给出正整数 s s s,和数列 a a a,记 f ( a ) f(a) f(a)表示:将序列分成 x x x块子序列,并保证每一块子序列的和都小于等于 s s s。记 f ( a ) f(a) f(a)为其中最小的 x x x

现在给定长度为 n n n的数列 a a a,求 ∑ 1 ≤ l ≤ r ≤ n ​ f ( ( a l ​ , a l + 1 ​ , ⋯ , a r ​ ) ) ∑1≤l≤r≤n​f((a_l​,a_{l+1}​,⋯,a_r​)) 1lrnf((al,al+1,,ar)).

分析:

考虑贪心,从左往右考虑这个序列,如果上一段能放下就放进上一段,否则新开一段。再考虑左端点相同时每个右端点的答案,设这个和为 f l f_l fl。分为两种情况:

  • 只有一段,对答案的贡献为 1 1 1
  • 有至少两段,但根据上面的贪心,它们第一段结束的位置均相同。设这个位置是 x x x,那么满足 r ≥ x r \ge x rx的区间可以看作 [ x , r ] [x,r] [x,r]答案 + 1 +1 +1,即 f l = f x + ( n − l + 1 ) f_l=f_x+(n-l+1) fl=fx+(nl+1)

求和的 a n s = ∑ i = 1 i = n f i ans=\sum\limits_{i=1}^{i=n}f_i ans=i=1i=nfi

代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int N = 3e5 + 5;
const int inf = 0x3f3f3f3f;
LL a[N], sum[N], f[N];

int main() {
    LL n, s;
    cin >> n >> s;
    for (int i = 1; i <= n; i++)
        cin >> a[i], sum[i] = sum[i - 1] + a[i];
    for (int i = n; i; i--) {
        int nxt = lower_bound(sum + 1, sum + n + 1, sum[i - 1] + s + 1) - sum;
        f[i] = f[nxt] + (n - i + 1);
    }
    LL ans = 0;
    for (int i = 1; i <= n; i++)
        ans += f[i];
    cout << ans << endl;
    return 0;
}

C.Not So Consecutive(dp)

题意:

给出长度为 n n n的序列 a a a,定义一个序列 a a a是好的,要求:

  • 1 ≤ a i ≤ n ≤ 5000 1 \le a_i \le n \le 5000 1ain5000
  • 序列 a a a中没有任何一个数字 x x x的连续出现次数 > x >x >x

现在给定一个长度为 n n n的序列 a a a,序列中的元素不是 − 1 -1 1就是 1 − n 1-n 1n中的任意一个数,请将所有的 − 1 -1 1替换成 1 − n 1-n 1n的任意数字,计算最后结果有多少不同的序列 a a a是好的,并将结果对 998244353 998244353 998244353进行取模。

分析:

f [ i ] [ j ] f[i][j] f[i][j]表示填了前 i i i个位置,第 j j j个位置填的数字是 j j j的方案数。枚举这个连续段的起点,得到转移为:
f i , j = ∑ k = m a x ( 0 , i − j ) i − 1 [ ∀ l ∈ [ k + 1 , i ] , A l = − 1 o r A l = j ] ∑ c o l ≠ j f k , c o l f_{i,j}= \sum\limits_{k=max(0,i-j)}^{i-1}[∀l∈[k+1,i],A_l=−1 or A_l=j]\sum\limits_{col\neq j}f_{k,col} fi,j=k=max(0,ij)i1[l[k+1,i],Al=1orAl=j]col=jfk,col
方括号里的式子是为了找 i i i前面最后一个填了不为 j j j 的数的位置,设这个位置为 l s t lst lst。对每个数维护 p o s j pos_j posj 表示数 j j j当前最后一次出现的位置。那么对于每个新的 i i i,我们记录 p o s pos pos 的最大值和次大值。
同时将 c o l ≠ j col \neq j col=j这个条件容斥掉,得到最后转移方程为:
f i , j = ∑ k = l s t i − 1 ∑ c o l = 1 n f k , c o l − ∑ k = l s t i − 1 f k , j f_{i,j}=\sum\limits_{k=lst}^{i-1} \sum\limits_{col=1}^{n} f_{k,col}-\sum\limits_{k=lst}^{i-1}f_{k,j} fi,j=k=lsti1col=1nfk,colk=lsti1fk,j
s i , j = ∑ k = 1 i f k , j s_{i,j}=\sum\limits_{k=1}^{i}f_{k,j} si,j=k=1ifk,j, s u m i = ∑ j = 1 i ∑ c o l = 1 n f i , c o l sum_i=\sum\limits_{j=1}^{i} \sum\limits_{col=1}^{n}f_{i,col} sumi=j=1icol=1nfi,col
前缀和优化, n 2 n^2 n2转移即可。

代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int N = 5e3 + 5;
const int inf = 0x3f3f3f3f;
const int mod = 998244353;
int pos[N], f[N][N], s[N][N], sum[N], n, a[N];

int main() {
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    f[0][0] = 1, sum[0] = 1;
    for (int i = 1; i <= n; i++) {
        if (a[i] != -1)
            pos[a[i]] = i;
        int mx1 = 0, mx2 = 0;
        for (int j = 1; j <= n; j++) {
            if (pos[j] > mx1)
                mx2 = mx1, mx1 = pos[j];
            else if (pos[j] > mx2)
                mx2 = pos[j];
        }
        for (int j = 1; j <= n; j++) {
            int lst = max(i - j, (a[mx1] == j) ? mx2 : mx1);
            f[i][j] = sum[i - 1] - (lst ? sum[lst - 1] : 0) - (s[i - 1][j] - (lst ? s[lst - 1][j] : 0));
            f[i][j] = (f[i][j] % mod + mod) % mod;
            s[i][j] = (s[i - 1][j] + f[i][j]) % mod;
            (sum[i] += f[i][j]) %= mod;
        }
        (sum[i] += sum[i - 1]) %= mod;
    }
    cout << (sum[n] - sum[n - 1] + mod) % mod << endl;
    return 0;
}

D Add to Make a Permutation(思维)

题意:

给你一个序列 a a a 0 ≤ a i ≤ n − 1 0 \le a_i \le n-1 0ain1,你可以进行任意次以下操作:

  • 任意选择 m m m个元素,把他们的值增加 1 1 1并对 n n n取模。

询问能否通过任意次操作使得 a a a变成一个 0 − n − 1 0-n-1 0n1的排列。

分析:

设序列 a a a 操作得到未被取模的最终序列 b b b

b b b一定满足以下条件:

  • b i ≤ a i b_i \le a_i biai

  • b i % n b_i \% n bi%n两两不同

  • s = ∑ i = 1 n ( b i − a i ) s=\sum\limits_{i=1}^{n}(b_i-a_i) s=i=1n(biai),有 s % m = 0 s\%m=0 s%m=0

  • m a x ( b i − a i ) ≤ s m max({b_i−a_i}) \le \frac{s}{m} max(biai)ms

在一定的操作次数下 m a x ( b ∗ i − a i ) max({b*i−a_i}) max(biai) 越小越容易满足条件。将 a a a序列升序排序,最优的对应关系一定是将 b b b也升序排序。

发现答案只和 s s s 的值有关,有结论:若有解,一定存在一种最优方案形如 b = ( x , x + 1 , ⋯ , x + n ) b=(x,x+1,⋯,x+n) b=(x,x+1,,x+n)

证明:假设我们有一个最优的 b b b 序列,且 b n − b 1 b_n-b_1 bnb1>n。那么令 b 1 ← b n − n b_1←b_n−n b1bnn, b n ← b 1 + n b_n←b_1+n bnb1+n,依次对照上面的所有条件:

  • 因为 b n − n > b 1 b_n−n>b_1 bnn>b1,新的 b 1 b_1 b1 比原来大。同时因为 b 1 > a 1 b_1>a_1 b1>a1 , a n − a 1 < n a_n − a_1 < n ana1<n,有 b 1 + n > a n b_1+n>a_n b1+n>an,满足条件 1 1 1

  • 加减 n n n不改变取模后的值。

  • 总和没变,S 不变。

  • b n − a n b_n-a_n bnan 一定变小。前者有 b n − a n > b n − n − a 1 b_n-a_n>b_n-n-a_1 bnan>bnna1,所以也变小,满足条件。

那么我们只需考虑最小化 x x x的值。根据第一个条件可以得到 x x x的下界。第四个条件 x x x每增加 1,不等式左侧增加 1 1 1,右侧增加 n m \frac{n}{m} mn。因为 n > m n>m n>m,第四个条件也为 x x x提供了下界。
接下来令 x x x满足第三个条件即可,发现 x x x x + n x+n x+n 是等价的, x x x 可以直接枚举 n n n次。

代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int N = 3e5 + 5;
const int mod = 998244353;
LL n, m, a[N], sum, x;

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    sort(a + 1, a + n + 1);
    for (int i = 1; i <= n; i++)
        x = max(x, a[i] - i + 1);
    for (int i = 1; i <= n; i++)
        sum += (x + i - 1) - a[i];
    bool flag = 0;
    for (int i = 0; i <= n; i++)
        if ((sum + n * i) % m == 0) {
            x += i, sum += n * i, flag = 1;
            break;
        }
    if (!flag) {
        cout << -1 << endl;
        return 0;
    }
    LL mx = 0;
    for (int i = 1; i <= n; i++)
        mx = max(mx, x + i - 1 - a[i]);
    while (mx > sum / m)
        mx += m / __gcd(n, m), sum += n * m / __gcd(n, m);
    cout << sum / m << endl;
    return 0;
}

E Avoid Boring Matches(思维)

题意:

2 n 2^n 2n个人,参加一项锦标赛。规则如下:

  • 每个人都有一顶颜色为红色或者蓝色的帽子,由字符串 S S S给出。
  • 重复以下操作,直到只剩下一个参与者:假设 2 k 2k 2k是当前的参与者人数。将参与者分成两组。可以自由选择如何配对它们。 然后,每组举行一场比赛,赢家留下来,输家离开比赛。 参与者按强度降序编号,因此编号较小的参与者总是获胜。

两个戴着红帽子的参与者之间的比赛称为无聊的比赛。 你的目标是安排配对,这样在比赛期间就不会发生无聊的比赛。
现在你可以执行任意次以下操作:

  • 选择 S S S中相邻的两个字符并交换他们。

询问是否有可能实现目标。 如果是,请查找所需的最小操作数。

分析:

R R R B B B 多一定无解,否则可以换成 B B B B . . . R R R BBBB...RRR BBBB...RRR的形式,一定有解。
先找判断序列合法的充要条件。考虑每轮的最优匹配策略,我们希望更多的 B B B能留到下一轮,因此要尽可能地让 B B B 和它后面的 R R R 配对。

在多种配对方案能保留的 $B $数量相同时,我们希望留下的 $B $位置尽可能靠前,因为这样在下下轮它们被保留下来的概率更大。因此我们得到这样的贪心策略:从左到右考虑每个 B B B,将它和右边第一个未被配对的 R R R配对。最终将所有剩下未配对的数两两配对。根据上文可以知道这样做是最优的。设 t i t_i ti表示长度为 2 i 2^i 2i,且为所有 B B B尽可能靠右的合法解。

t 0 t_0 t0= R R R,考虑从 t i − 1 t_{i−1} ti1 得到 t i t_i ti。从左到右处理 t i − 1 t_{i−1} ti1 的每一位,若当前位是 R R R,表示这位没被匹配, t i = t i + R t_i=t_i+R ti=ti+R;当前位是 B B B,我们希望下一个 B B B尽量靠后,即与之匹配的 R R R尽量更近, t i = t i + B R t_i=t_i+BR ti=ti+BR。不足 2 i 2^i 2i 位用 B B B 补齐。

t n t_n tn的第 j j j B B B 的位置为 T j T_j Tj,原序列位置为 S j S_j Sj。那么若存在 S j > T j S_j>T_j Sj>Tj,则该序列不合法。证明大概是如果一个 S j < T j S_j < T_j Sj<Tj,匹配数不会变多;但如果 S j > T j , B R S_j > T_j,BR Sj>Tj,BR的匹配数一定减少。

答案是令 S S S 满足上述条件的最小操作次数 a n s = ∑ j = 1 2 n m a x ( 0 , T j − S j ) ans=\sum\limits_{j=1}^{2^n}max(0,T_j-S_j) ans=j=12nmax(0,TjSj)

代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int inf = 0x3f3f3f3f;
const int N = (1 << 18) + 5;
int n;
string s, t[N];
int tot, pos[N];

int main() {
    cin >> n;
    cin >> s;
    t[0] = "R";
    for (int i = 1; i <= n; i++) {
        for (auto c: t[i - 1])
            if (c == 'R')
                t[i] += "R";
            else
                t[i] += "BR";
        while (t[i].size() < (1 << i))
            t[i] += "B";
    }
    for (int i = 0; i < (1 << n); i++)
        if (t[n][i] == 'B')
            pos[++tot] = i;
    tot = 0;
    long long ans = 0;
    for (int i = 0; i < (1 << n); i++)
        if (s[i] == 'B') {
            tot++;
            if (i > pos[tot])
                ans += i - pos[tot];
            if (tot == (1 << n - 1))
                break;
        }
    if (tot < (1 << n - 1))
        cout << -1 << endl;
    else
        cout << ans << endl;
    return 0;
}

学习交流

以下为学习交流QQ群,群号: 546235402,每周题解完成后都会转发到群中,大家可以加群一起交流做题思路,分享做题技巧,欢迎大家的加入。

  • 22
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值