AtCoder Regular Contest 182 A~D

A.Chmax Rush!(枚举)

题意:

有一个长度为 N N N的整数序列 S S S。最初, S S S的所有元素都是 0 0 0

同时给你两个长度为 Q Q Q的整数序列: P = ( P 1 , P 2 , … , P Q ) P=(P_1,P_2,\dots,P_Q) P=(P1,P2,,PQ) V = ( V 1 , V 2 , … , V Q ) V=(V_1,V_2,\dots,V_Q) V=(V1,V2,,VQ)

斯努克希望依次对序列 S S S执行 Q Q Q次操作。第 i i i个操作如下:

  • 执行以下操作之一:
    • 将每个元素 S 1 , S 2 , … , S P i S_1,S_2,\dots,S_{P_i} S1,S2,,SPi替换为 V i V_i Vi。但是,在执行此操作前,如果 S 1 , S 2 , … , S P i S_1,S_2,\dots,S_{P_i} S1,S2,,SPi中存在严格大于 V i V_i Vi的元素,斯努克就会开始哭泣。
    • 将每个元素 S P i , S P i + 1 , … , S N S_{P_i},S_{P_i+1},\dots,S_N SPi,SPi+1,,SN替换为 V i V_i Vi。但是,在此操作之前,如果 S P i , S P i + 1 , … , S N S_{P_i},S_{P_i+1},\dots,S_N SPi,SPi+1,,SN中有元素严格大于 V i V_i Vi,斯努克就会开始哭泣。

求在 Q Q Q的操作序列中,斯努克可以在不哭泣的情况下完成所有操作的次数,答案对 998244353 998244353 998244353取模。

当且仅当有 1 ≤ i ≤ Q 1\leq i\leq Q 1iQ使得第 i i i次操作的选择不同时,两个操作序列是不同的。

分析:

考虑对于任意一个 i < j i\lt j i<j,如果 V i > V j V_i>V_j Vi>Vj,那么 i i i j j j就会互相干扰。

如果 P i = P j P_i = P_j Pi=Pj,那么 j j j不管怎么选操作最后在 P i P_i Pi这个点都会出现 S P i > V j S_{P_i} > V_j SPi>Vj的现象,此时答案为 0 0 0

如果 P i ≠ P j P_i \neq P_j Pi=Pj,那么让 P P P小的那个执行操作 1 → P 1 \rightarrow P 1P,大的那个 P → n P \rightarrow n Pn

记录一个方向数组,如果 s d i = 1 sd_i=1 sdi=1代表必须往 n n n操作, s d i = − 1 sd_i=−1 sdi=1代表必须往 1 1 1操作, s d i = 0 sd_i=0 sdi=0代表无约束,枚举 i , j i,j i,j即可,如果出现既要一个操作向左又要向右也是无解的。

答案统计就遍历 1 → Q 1 \rightarrow Q 1Q a n s ans ans初始设为 1 1 1,如果 s d i = 0 sd_i = 0 sdi=0就把答案乘 2 2 2

代码:

#include<bits/stdc++.h>

using namespace std;
const int mod = 998244353;
const int N = 5005;
int P[N], V[N];
int n, q, sd[N], ans = 1;

int main() {
    cin >> n >> q;
    for (int i = 1; i <= q; i++) {
        cin >> P[i] >> V[i];
    }
    for (int j = 1; j <= q; j++) {
        for (int i = j + 1; i <= q; i++) {
            if (V[i] >= V[j])
                continue;
            if (P[i] == P[j]) {
                cout << 0;
                return 0;
            }
            if (P[j] < P[i]) {
                if (sd[i] && sd[i] != 1) {
                    cout << 0;
                    return 0;
                }
                if (sd[j] && sd[j] != -1) {
                    cout << 0;
                    return 0;
                }
                sd[j] = -1, sd[i] = 1;
            } else {
                if (sd[i] && sd[i] != -1) {
                    cout << 0;
                    return 0;
                }
                if (sd[j] && sd[j] != 1) {
                    cout << 0;
                    return 0;
                }
                sd[j] = 1, sd[i] = -1;
            }
        }
    }
    for (int i = 1; i <= q; i++)
        ans = ans * ((1 + (sd[i] == 0))) % mod;
    cout << ans << endl;
    return 0;
}

B.|{floor(A_i/2^k)}|(构造)

题意:

给你正整数 N N N K K K

长度为 N N N的整数序列,其中所有元素都在 1 1 1 2 K − 1 2^K-1 2K1之间(含),称为良好序列

良好序列 A = ( A 1 , A 2 , … , A N ) A=(A_1,A_2,\ldots,A_N) A=(A1,A2,,AN)得分定义如下:

  • 使用介于 1 1 1 N N N(含)之间的整数 i i i和非负整数 k k k可以表示为 ⌊ A i 2 k ⌋ \displaystyle\left\lfloor\frac{A_i}{2^k}\right\rfloor 2kAi的不同整数的个数。

例如,对于 A = ( 3 , 5 ) A=(3,5) A=(3,5),五个整数可以表示为 ⌊ A i 2 k ⌋ \displaystyle \left\lfloor\frac{A_i}{2^k}\right\rfloor 2kAi 0 0 0 1 1 1 2 2 2 3 3 3 5 5 5,因此得数为 5 5 5

找出一个得分最高的好序列。

每个输入文件都有 T T T个测试用例需要解决。

分析:

看到除以 2 k 2^k 2k,联想到与位运算有关,想要得到更多的分数那么右移后得到的数就要越多,所以需要构造出更多不同的二进制下位数不超过 k k k的数。

从最高位开始,使用类似线段树建树的方式,每次为左边的区间按位或上 1 1 1,右边的区间不进行处理。 k k k 0 0 0时直接结束,若递归到叶子节点时未满 k k k位,后面全部填 1 1 1即可。这样二分着构造出来的所有数均不同且不断右移后能得到更多的结果。

代码:

#include<bits/stdc++.h>

using namespace std;
const int N = 1e5 + 5;
int ans[N << 1];

void solve(int l, int r, int k) {
    if (!k)
        return;
    if (l == r) {
        while (k--)
            ans[l] = (ans[l] << 1) + 1;
        return;
    }
    int mid = (l + r) >> 1;
    for (int i = l; i <= mid; i++)
        ans[i] = (ans[i] << 1) + 1;
    for (int i = mid + 1; i <= r; i++)
        ans[i] <<= 1;
    solve(l, mid, k - 1);
    solve(mid + 1, r, k - 1);
}

int n, k;

int main() {
    int T;
    cin >> T;
    while (T--) {
        cin >> n >> k;
        for (int i = 1; i <= n; i++)
            ans[i] = 1;
        solve(1, n, k - 1);
        for (int i = 1; i <= n; i++)
            cout << ans[i] << " ";
        cout << endl;
    }
    return 0;
}

C.Sum of Number of Divisors of Product(数学)

题意:

长度在 1 1 1 N N N之间(含)的整数序列,其中每个元素的长度在 1 1 1 M M M之间(含),称为好序列

好序列的分数定义为 X X X的正除数,其中 X X X是序列中各元素的乘积。

好序列有 ∑ k = 1 N M k \displaystyle\sum_{k=1}^{N}M^k k=1NMk个。求所有这些序列的分数之和对 998244353 998244353 998244353取模的结果。

分析:

m ≤ 16 m≤16 m16,先找出所有质数 2 , 3 , 5 , 7 , 11 , 13 2,3,5,7,11,13 2,3,5,7,11,13,一共有 6 6 6个,考虑状压也只有 64 64 64

进一步发现 n n n巨大,但是状压状态很少,可以想到矩阵快速幂。考虑怎么构造原 D P DP DP序列和矩阵进行转移:

首先要求对于每个长度求和所以肯定要设一维来统计前缀和。

其次我们写出转移,并构造转移矩阵:发现如果设“某一状态为某一质数有没有”无法转移,所以我们先令第 i ( 0 ≤ i < 6 ) i(0≤i<6) i(0i<6)个质数的编号为 i i i,个数为 a i a_i ai a i = 0 a_i=0 ai=0表示没有这个质数),则显然一个序列的答案为 ( a 0 + 1 ) ( a 1 + 1 ) ( a 2 + 1 ) ( a 3 + 1 ) ( a 4 + 1 ) ( a 5 + 1 ) (a_0+1)(a_1+1)(a_2+1)(a_3+1)(a_4+1)(a_5+1) (a0+1)(a1+1)(a2+1)(a3+1)(a4+1)(a5+1)而如果我们在序列末尾加入一个 6 6 6则答案就变成 ( a 0 + 2 ) ( a 1 + 2 ) ( a 2 + 1 ) ( a 3 + 1 ) ( a 4 + 1 ) ( a 5 + 1 ) (a_0+2)(a_1+2)(a_2+1)(a_3+1)(a_4+1)(a_5+1) (a0+2)(a1+2)(a2+1)(a3+1)(a4+1)(a5+1)

根据 c ( a + x ) ( b + y ) = c ( a b + a y + b x + x y ) c(a+x)(b+y)=c(ab+ay+bx+xy) c(a+x)(b+y)=c(ab+ay+bx+xy),设 c = ( a 2 + 1 ) ( a 3 + 1 ) ( a 4 + 1 ) ( a 5 + 1 ) c=(a_2+1)(a_3+1)(a_4+1)(a_5+1) c=(a2+1)(a3+1)(a4+1)(a5+1)原式子即为: ( a 0 + 2 ) ( a 1 + 2 ) c = ( a 0 + 1 ) ( a 1 + 1 ) c + ( a 0 + 1 ) c + ( a 1 + 1 ) c + c (a_0+2)(a_1+2)c=(a_0+1)(a_1+1)c+(a_0+1)c+(a_1+1)c+c (a0+2)(a1+2)c=(a0+1)(a1+1)c+(a0+1)c+(a1+1)c+c

设某一状态 f S f_S fS表示 ( a i + 1 ) ( a j + 1 ) ( a k + 1 ) ⋯ ( a l + 1 ) , ( i , j , k , … , l ∈ S ) (a_i+1)(a_j+1)(a_k+1)⋯(a_l+1),(i,j,k,…,l\in S) (ai+1)(aj+1)(ak+1)(al+1),(i,j,k,,lS)的和。

那么转移过程就变成我们枚举每一位和 [ 1 , m ] [1,m] [1,m]间的每一位数字,来看看对于原序列的系数是多少,然后给转移矩阵增加即可,因为一个数字最多有两个不同的质数,找两个变量记录一下即可。

式子形如: f S = f S + f S − i + f S − j + f S − i − j f_S=f_S+f_{S−{i}}+f_{S−{j}}+f_{S−{i}−{j}} fS=fS+fSi+fSj+fSij

代码:

#include<bits/stdc++.h>

typedef long long LL;
using namespace std;
const LL N = 66;
const LL mod = 998244353;

struct Matrix {
    LL n, m, num[N][N];

    Matrix() {
        n = m = 0;
        memset(num, 0, sizeof(num));
    }
};

Matrix operator*(const Matrix &x, const Matrix &y) {
    Matrix c;
    c.n = x.n;
    c.m = y.m;
    for (LL i = 0; i < x.n; i++)
        for (LL j = 0; j < y.m; j++)
            for (LL k = 0; k < x.m; k++)
                (c.num[i][j] += x.num[i][k] * y.num[k][j] % mod) %= mod;
    return c;
}

Matrix ksm(Matrix a, LL b) {
    Matrix t;
    t.n = t.m = a.n;
    for (LL i = 0; i < a.n; i++)t.num[i][i] = 1;
    for (; b; b >>= 1, a = a * a)if (b & 1)t = t * a;
    return t;
}

LL n, m;
LL id[17];
Matrix a, b;

int main() {
    id[2] = 1;
    id[3] = 2;
    id[5] = 3;
    id[7] = 4;
    id[11] = 5;
    id[13] = 6;
    cin >> n >> m;
    a.n = 1, a.m = 65;
    for (LL i = 0; i < 64; i++)
        a.num[0][i] = 1;
    b.n = b.m = 65;
    b.num[64][64] = b.num[63][64] = 1;
    for (LL i = 0; i < 64; i++)
        for (LL k = 1; k <= m; k++) {
            LL t = k, t1 = -1, t2 = -1, s1 = 0, s2 = 0;
            for (LL l = 2; l <= t; l++)
                if (t % l == 0) {
                    if (~t1) {
                        t2 = id[l] - 1;
                        while (t % l == 0)t /= l, s2++;
                    } else {
                        t1 = id[l] - 1;
                        while (t % l == 0)t /= l, s1++;
                    }
                }
            b.num[i][i]++;
            if (t1 >= 0 && (i >> t1 & 1))b.num[i ^ (1 << t1)][i] += s1;
            if (t2 >= 0 && (i >> t2 & 1))b.num[i ^ (1 << t2)][i] += s2;
            if (t1 >= 0 && (i >> t1 & 1) && t2 >= 0 && (i >> t2 & 1))b.num[i ^ (1 << t1) ^ (1 << t2)][i] += s1 * s2;
        }
    a = a * ksm(b, n + 1);
    cout << a.num[0][64] - 1 << endl;
    return 0;
}

D.Increment Decrement Again(思维)

题意:

没有两个相邻元素相同的整数序列称为良好序列

给你两个长度为 N N N的良好序列: A = ( A 1 , A 2 , … , A N ) A=(A_1,A_2,\dots,A_N) A=(A1,A2,,AN) B = ( B 1 , B 2 , … , B N ) B=(B_1,B_2,\dots,B_N) B=(B1,B2,,BN) A A A B B B中的每个元素都介于 0 0 0 M − 1 M-1 M1之间。

您可以对 A A A执行以下任意次数的运算,可能为零次:

  • 1 1 1 N N N之间选择一个整数 i i i,并执行以下操作之一:
    • A i ← ( A i + 1 )   m o d   M A_i\leftarrow(A_i+1)\bmod M Ai(Ai+1)modM
    • A i ← ( A i − 1 )   m o d   M A_i\leftarrow(A_i-1)\bmod M Ai(Ai1)modM。此处为 ( − 1 )   m o d   M = M − 1 (-1)\bmod M=M-1 (1)modM=M1

但是,不能进行使 A A A不再是一个好序列的操作。

判断是否有可能使 A A A等于 B B B,如果有可能,求这样做所需的最小运算次数。

分析:

先特判 m = 2 m=2 m=2

然后我们看作 A A A不对 m m m取模,要求变为 ∀ i < n \forall i\lt n i<n ∣ a i − a i + 1 ∣ < m ∧ a i ≠ a i + 1 |a_i−a_{i+1}|\lt m∧a_i≠a_{i+1} aiai+1<mai=ai+1

可以发现 a a a的大小关系是不会变的,所以如果固定了 a 1 ′ a^′_1 a1,则后面的都是固定的。可以先令 a 1 ′ = b 1 a^′_1=b_1 a1=b1,根据大小关系求出任意一组合法的 a ′ a^′ a,接下来问题变为最小化 ∑ i = 1 n ∣ a i − a i ′ − k m ∣ \sum\limits^n_{i=1}|a_i−a^′_i−km| i=1naiaikm,可以二分类似找中位数的方法解决。复杂度 O ( n l o g n l o g V ) O(nlognlogV) O(nlognlogV)

代码:

#include<bits/stdc++.h>

typedef long long LL;
const LL inf = 0x3f3f3f3f;
using namespace std;

LL n, m, L, R, mid, p, a[200010], b[200010], c[200010];

int main() {
    cin >> n >> m;
    for (LL i = 1; i <= n; ++i)
        cin >> a[i];
    for (LL i = 1; i <= n; ++i)
        cin >> b[i];
    if (m == 2) {
        if (a[1] == b[1])
            cout << "0" << endl;
        else
            cout << "-1" << endl;
        return 0;
    }
    c[1] = b[1];
    for (LL i = 2; i <= n; ++i) {
        LL val = (b[i] - b[i - 1] + m) % m;
        if (a[i] > a[i - 1])
            c[i] = c[i - 1] + val;
        else
            c[i] = c[i - 1] + val - m;
    }
    for (LL i = 1; i <= n; ++i)
        c[i] = a[i] - c[i];
    sort(c + 1, c + 1 + n);
    L = -1000 * inf;
    R = 1000 * inf;
    while (L < R) {
        mid = L + ((R - L + 1) >> 1);
        p = lower_bound(c + 1, c + 1 + n, mid * m) - c;
        if (p <= (n + 1) / 2)
            L = mid;
        else
            R = mid - 1;
    }
    LL ans = 0, s = 0;
    for (LL i = 1; i <= n; ++i)
        ans += abs(c[i] - L * m);
    for (LL i = 1; i <= n; ++i)
        s += abs(c[i] - (L + 1) * m);
    cout << min(ans, s) << endl;
    return 0;
}

赛后交流

在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。

群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值