CSP-J2022 山东 题解

[CSP-J2022 山东] 植树节

题目背景

受疫情影响,山东省取消了 CSP-J 2022 认证活动,并于次年三月重新命题,在省内补办比赛。

题目描述

植树节快要到了,学校要组织志愿者去给树苗浇水。

有一排树苗,编号依次是 0,1,2,…0,1,2,\dots0,1,2,

现有 nnn 个志愿者去给树苗浇水,第 iii 个志愿者选定了一个区间 [ai,bi]\left[a_{i},b_{i}\right][ai,bi] ,表示第 iii 个志愿者将 [ai,bi]\left[a_{i},b_{i}\right][ai,bi] 这一区间内的每一棵树都浇一次水。

如某个志愿者选择的浇水区间为 [4,9]\left[4,9\right][4,9] ,表示他将给编号为 4,5,6,7,8,94,5,6,7,8,94,5,6,7,8,9 的树各浇水一次。

当所有的志愿者完成各自所选区间的浇水后,可能有些树苗被不同的志愿者浇水多次,也可能有的树苗一次也没被浇过水。

请你求出浇水最多的树苗被浇了多少次。

输入格式

111 行,一个整数 nnn ,表示志愿者的人数。

222 行到第 n+1n+1n+1 行,每行两个整数 $a_{i},b_{i} ((i=0,1,2,\dots n-1$),表示志愿者 iii 选择的浇水区间。

输出格式

输出 111 行, 111 个整数,表示浇水最多的树苗被浇水的次数。

输入输出样例 #1

输入 #1

4
0 2
2 4
1 4
6 7

输出 #1

3

输入输出样例 #2

输入 #2

4
1000000 1000000
1000000 1000000
0 1000000
1 1000000

输出 #2

4

说明/提示

数据范围

  • 对于所有的数据: n<105n<10^{5}n<1050≤ai≤bi≤1060\le a_{i}\le b_{i}\le 10^{6}0aibi106
测试点编号ai≤a_{i}\leaibi≤b_{i}\lebini≤n_{i}\leni特殊性质
1,2,31,2,31,2,310310^{3}10310310^{3}10310310^{3}103
4,5,6,74,5,6,74,5,6,710610^{6}10610610^{6}10610510^{5}105
88810610^{6}10610610^{6}10610510^{5}105ai=bia_{i}=b_{i}ai=bi
99910610^{6}10610610^{6}10610510^{5}105ai=1,bi=103a_{i}=1,b_{i}=10^{3}ai=1,bi=103
10101010610^{6}10610610^{6}10610510^{5}105

解题思路

很模板的一道差分题目,操作是区间元素全部加上一个数,在最后单点查询,竟然不需要开 long long,不知道怎么想的出这个题。

#include <bits/stdc++.h>

using namespace std;

const int maxt = 1e6 + 5;
int n, d[maxt]; // d 表示差分数组,一开始都是 0

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        int l, r;
        cin >> l >> r; 
        d[l]++; // 差分操作
        d[r + 1]--;
    }
    int ans = 0, ti = 0; // ti 表示当前这个地方被浇了多少次水,ans 是最大次数
    for (int i = 0; i <= 1000000; i++)
    {
        ti += d[i];
        ans = max(ans, ti);
    }
    cout << ans;
    return 0;
}

[CSP-J2022 山东] 宴会

题目背景

受疫情影响,山东省取消了 CSP-J 2022 认证活动,并于次年三月重新命题,在省内补办比赛。

题目描述

今人不见古时月,今月曾经照古人。梦回长安,大唐风华,十里长安花,一日看尽。

唐长安城是当时世界上规模最大、建筑最宏伟、规划布局最为规范化的一座都城。其营建制度规划布局的特点是规模空前、创设皇城、三城层环、六坡利用、布局对称、街衢宽阔、坊
里齐整、形制划一、渠水纵横、绿荫蔽城、郊环祀坛。而所谓的十里长安街,位于长安城的中轴线上,即唐长安城的朱雀大街,又称承天门大街。唐朝官员们住在各个“坊”里,上朝下朝都需要通过朱雀大街。

为了保持各大家族的联系和友谊,各官员往往会每月办一次宴会。为了方便描述,我们把朱雀大街看成一个数轴,各官员所居住的“坊”缩略为数轴上的一个坐标点。大家决定选一处地点(该地点是数轴上的某一个点,不一定坐标点)办宴会。由于唐朝宵禁严格,大家又都希望交流时间尽可能长,因此想要使宴会开始时间尽可能早。又因为大唐注重礼仪,因此,参加宴会的官员会花一定时间盛装打扮过后才前往宴会地点(不一定是坐标点)。

更具体地,一条纵向的街道上(相当于一维坐标)有 nnn 个人居住,其中第 iii 个人居住在 xix_{i}xi (非负整数)位置(坐标点)上。每月他们会选择在 x0x_{0}x0(数轴上的某一个点,不一定坐标点)出举办宴会。

已知第 iii 个人从 xix_{i}xi 出发前往宴会地点 x0x_{0}x0 处需要花费 ∣xi−x0∣\left|x_{i}-x_{0}\right|xix0 的时间,另外,他还需要花费 tit_{i}ti 的时间进行打扮,换言之,他共需要花费 ∣xi−x0∣+ti\left|x_{i}-x_{0}\right|+t_{i}xix0+ti 的时间到达宴会举办处。

假设初始时刻为 000。这 nnn 个人开始打扮和出发前往宴会处,他们想要使得宴会的开始时间尽可能早,于是向你求助,请你帮助他们确定好最优的宴会举办地点 x0x_{0}x0

注:∣xi−x0∣\left|x_{i}-x_{0}\right|xix0 表示 xix_{i}xix0x_{0}x0 之差的绝对值,且题目中 nnn 个人的居住地点坐标均为整数。

输入格式

第一行一个正整数 TTT,表示测试数据的组数。

接下来对于每组测试数据(注意:每组测试数据有 333 行数据,以下共 3×T3\times T3×T 行数据):

第一行一个正整数 nnn,表示总官员人数。

第二行共 nnn 个非负整数 x1,x2,…,xnx_{1},x_{2},\dots,x_{n}x1,x2,,xn 分别表示这 nnn 个人在数轴上的坐标。

第三行共 nnn 个非负整数 t1,t2,…,tnt_{1},t_{2},\dots,t_{n}t1,t2,,tn 分别表示这 nnn 个人出发前的打扮时间。

输出格式

共输出 TTT 行数据,对于每组测试数据,输出一行一个实数(如果是整数按整数输出,如果有小数,保留 111 位小数输出),表示使宴会开始时间最早的最优举办地点坐标 x0x_{0}x0。(很显然,x0x_{0}x0 都是唯一的)

输入输出样例 #1

输入 #1

7
1
0
3
2
3 1
0 0
2
1 4
0 0
3
1 2 3
0 0 0
3
1 2 3
4 1 2
3
3 3 3
5 3 3
6
5 4 7 2 10 4
3 2 5 1 4 6

输出 #1

0
2
2.5
2
1
3
6

说明/提示

样例说明

初始时刻为 000

对于第一组测试数据只有 111 个人,坐标为 000,打扮时间为 333,很显然 x0x_{0}x0 就定在坐标 000 处,使得宴会开始时间为 333 且最早。

对于第二组测试数据有 222 个人,坐标分别为 333111,打扮时间均为 000,很显然 x0x_{0}x0 定在坐标 222 处,使得宴会开始时间为 111 且最早。

对于第三组测试数据有 222 个人,坐标分别为 111444,打扮时间均为 000,很显然 x0x_{0}x0 定在坐标 2.52.52.5 处,使得宴会开始时间为 1.51.51.5 且最早。

数据范围

对于 30%30\%30% 的数据,T=1,n≤100,0≤xi,ti≤1000T=1,n\le100,0\le x_{i},t_{i}\le1000T=1,n100,0xi,ti1000

对于 60%60\%60% 的数据,n≤104,0≤xi,ti≤105n\le10^{4},0\le x_{i},t_{i}\le10^{5}n104,0xi,ti105

对于 100%100\%100% 的数据,1≤T≤103,n≤105,0≤xi,ti≤1081\le T\le10^{3},n\le10^{5},0\le x_{i},t_{i}\le10^{8}1T103,n105,0xi,ti108,且保证所有测试数据的 nnn 加起来不超过 2×1052\times10^{5}2×105

解题思路

首先把所有人按照坐标从小到大的顺序排序(相当于从左到右在大街上排列),这个就不多说了。

我们要排除一种情况,就是聚会地点坐标比所有的 “坊” 都大或者都小的情况。我们用贪心的思想去看待这个问题:如果当前聚会地点坐标比所有的 “坊” 都大或者都小(我们以都大举例),然后我把这个地点移动到坐标最大的 “坊” 的位置,那么大家的移动距离都变小了,所需的时间也就都变小了,最终答案肯定也会变小。

看看答案是怎么计算的,先计算每一个人到达聚会地点的时间,我们现在假设聚会地点的坐标为 posposposiii 是 “坊” 的编号,如果 xi≤posx_i \le posxipos,那么这个人到达聚会地点所需要的时间就是 pos−(xi−ti)pos - (x_i - t_i)pos(xiti);反之,如果 xi≥posx_i \ge posxipos,所需的时间就是 (xi+ti)−pos(x_i + t_i) - pos(xi+ti)pos

那么我们再从整体的视角上看这个问题,假设 iii 是聚会地点左边(坐标更小)的某一个 “坊” 的坐标, jjj 是聚会地点右边(坐标更大)的某一个 “坊” 的坐标。那么左边就需要 pos−min(xi−ti)pos - min(x_i - t_i)posmin(xiti) 的时间才能让所有人到达聚会地点;同理,右边就需要 max(xj+tj)−posmax(x_j + t_j) - posmax(xj+tj)pos 的时间,那么所有人到达聚会地点的时间就是二者之间的最大值。

最后,怎么确定 pospospos 的位置呢?先维护好两个数组 L[i]L[i]L[i]R[i]R[i]R[i]L[i]L[i]L[i] 表示坐标小于等于第 iii 个 “坊” 的所有的 “坊” 的 (x−t)(x-t)(xt) 的最小值。 R[i]R[i]R[i] 表示坐标大于iii 个 “坊” 的所有的 “坊” 的 (x+t)(x+t)(x+t) 的最大值。我们预先处理出来,用 for 循环从前从后扫一遍就行。然后现在我们枚举一个正整数 kkk,表示聚会地点在第 kkk 个和第 k+1k + 1k+1 个 “坊” 之间。这个位置越靠近 kkk,那么右边花费的时间就越多;相同的,越靠近 k+1k + 1k+1,左边花费的时间就更多,因为我们的目标是最大值最小,所以我们的目标是尽可能实现 pos−L[k]=R[k]−pospos - L[k] = R[k] - posposL[k]=R[k]pos,这个情况下答案是最小的,否则左边花费的时间或者右边花费的时间就会变大,最终答案也会变大。那么如果上面的这个式子成立不了怎么办(求出来的 pospospos 不在 kkkk+1k + 1k+1 之间)。

例如求出来 pos<xkpos < x_kpos<xk,那么说明左边花费的时间一定大于右边,最终答案是左边花费的时间,那么我们就让 pos=xkpos = x_kpos=xk 就行了;pos>xkpos > x_kpos>xk 同理。

时间复杂度就是排序的时间复杂度。

#include <bits/stdc++.h>

using namespace std;

const int maxn = 1e5 + 5;
int n, L[maxn], R[maxn];

struct Person // 用结构体方便排序
{
    int x, t;
} a[maxn];

void solve()
{
    double min_t = 1000000000, ans;
    cin >> n;
    L[0] = 1000000000;
    R[0] = -1000000000;
    for (int i = 1; i <= n; i++)
        cin >> a[i].x;
    for (int i = 1; i <= n; i++)
        cin >> a[i].t;
    if (n == 1) // 只有一个人就直接在他家开聚会
    {
        cout << a[1].x << '\n';
        return;
    }
    sort(a + 1, a + n + 1, [](const Person &x, const Person &y)
         { return x.x < y.x; }); // 排序,这里我偷懒了,大家自己写一个 cmp 就行,让坐标从小到大排序
    for (int i = 1; i < n; i++) // 一个是左边一个是右边,所以一个从前往后,一个从后往前
        L[i] = min(L[i - 1], a[i].x - a[i].t);
    for (int i = n; i >= 2; i--)
        R[i - 1] = max(R[i], a[i].x + a[i].t);
    for (int i = 1; i < n; i++)
    {
        double b = ((double)L[i] + R[i]) / 2, pos; // b 就是求出来的 pos
        if (b < a[i].x)
            pos = a[i].x;
        if (b > a[i + 1].x)
            pos = a[i + 1].x;
        if (b >= a[i].x && b <= a[i + 1].x)
            pos = b;
        if (max(pos - L[i], R[i] - pos) < min_t) // 如果答案更优就记录一下
            min_t = max(pos - L[i], R[i] - pos), ans = pos;
    }
    cout << ans << '\n';
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int T;
    cin >> T;
    while (T--) // 多组输入
        solve();
    return 0;
}

[CSP-J2022 山东] 部署

题目背景

受疫情影响,山东省取消了 CSP-J 2022 认证活动,并于次年三月重新命题,在省内补办比赛。

题目描述

“万里羽书来未绝,五关烽火昼仍传。”

古时候没有现代信息化战争的技术,只能靠烽火传信和将军运筹帷幄的调兵遣将来取得战争的优势。

为了使消耗最低,现在 A 国已经在 nnn 个城市之间建好了道路和行军部署渠道,使得这 nnn 个城市都能互相到达且不存在环(即构成以 111 号城市为根节点的树型结构)。每个城市都驻扎了一定数量的兵力。

为了清晰的描述问题,我们给这 nnn 个城市进行 111nnn 的编号,且 111 号城市为树的根节点(数据保证:构成以 111 号城市为根节点的一棵树)。初始时,第 iii 座城市拥有初始兵力 aia_{i}ai

现在为测试战争部署速度,将军进行了 mmm 次测试,每次测试可能为以下两种命令的某一种:

1 x y(三个数间均用 1 个空格分开):向 xxx 号城市以它为根的子树中的所有城市全部增兵 yyy 的数量。

2 x y(三个数间均用 1 个空格分开):向 xxx 号城市与它直接相连(含父结点和子结点)的城市全部增兵 yyy 的数量。

mmm 条命令发布出去后,将军喊来参谋,进行了 qqq 次询问,每次询问第 xxx 座城市的最终兵力情况。
该参谋就是小虾米,他又向你求助了,请你帮助他解决问题(qqq 次询问的结果)。

输入格式

第一行一个正整数 nnn 表示城市数量。

第二行一共 nnn 个正整数 a1,a2,…ana_{1},a_{2},\dots a_{n}a1,a2,an 表示每座城市的初始兵力。

接下来 n−1n-1n1 行,每行两个整数 x,yx,yx,y,表示 xxxyyy 城市之间有直接相连的道路。

接下来一行一个正整数 mmm,表示 mmm 次命令。

接下来 mmm 行,每行三个正整数 p,x,yp,x,yp,x,y 表示两种命令其中一种,其中 p=1p=1p=1 时表示第一种命令,p=2p=2p=2 时表示第二种命令。

接下来一行一个正整数 qqq,表示 qqq 次询问。

接下来 qqq 行,每行一个正整数 xxx ,表示询问编号为 xxx 的城市最后的兵力值。

输出格式

一共 qqq 行,每行一个正整数分别对应于每次询问的答案。

输入输出样例 #1

输入 #1

5
1 2 3 4 5
1 2
1 3
2 4
3 5
4
1 1 2
2 2 3
1 3 3
2 5 1
4
1
2
3
4

输出 #1

6
7
9
9

输入输出样例 #2

输入 #2

4
1 1 1 1
1 2
1 3
1 4
1
1 1 1
2
1
2

输出 #2

2
2

说明/提示

数据范围

对于 30%30\%30% 的数据,1≤n≤1000,1≤m≤10001\le n\le1000,1\le m\le10001n1000,1m1000

对于 60%60\%60% 的数据,1≤n≤105,1≤m≤105,1≤q≤1051\le n\le10^{5},1\le m\le10^{5},1\le q\le10^{5}1n105,1m105,1q105

其中 10%10\%10% 的数据树是一条链,另外 10%10\%10% 的数据只有 111 操作,另外 10%10\%10% 的数据只有 222 操作。

对于 100%100\%100% 的数据,数据保证给定的城市和道路能形成树,且 111 号城市为根节点。1≤n≤106,1≤m≤106,1≤q≤106,1≤ai≤109,1≤x≤n,1≤y≤101\le n\le10^{6},1\le m\le10^{6},1\le q\le10^{6},1\le a_{i}\le10^{9},1\le x\le n,1\le y\le101n106,1m106,1q106,1ai109,1xn,1y10

解题思路

首先我们先要简化一下这 mmm 次操作,例如:1 1 3 1 1 2 直接变成 1 1 5 就可以了,1 的子树全部加 3,然后 1 的子树再加 2,不就等价于一次加 5 嘛,然后 2 操作同理。

接下来就是代码问题了,只要你会用代码实现上面两种操作就行了,至于时间复杂度是很宽容的,1 操作只需要一次 O(n) 的 dfsdfsdfs 就行了,至于 2 操作这里给大家稍微简单的证明一下。
在这里插入图片描述
对于树上这样的一条边来说,最多会存在两次操作:uuuvvv 加上一个值、vvvuuu 加上一个值,2 操作导致一个点让一个他相邻的点的值加上一个值的操作最多出现 2×(n−1)2 \times (n - 1)2×(n1) 次,也是线性的。具体细节大家看下面的代码吧。

#include <bits/stdc++.h>

using namespace std;

const int maxn = 1e6 + 5;
int n, m, q, tot, head[maxn], down[maxn], near[maxn], num[maxn];
// down[x] 表示 x 和他的子树都要加上 down[x]
// near[x] 表示 x 和他周围的点要加上的数
// num[x] 表示这个点上有多少士兵

struct edge // 这里建图用的是链式前向星,不会的自行学习
{
    int nxt, to;
} e[maxn << 1];

void addedge(int u, int v)
{
    e[++tot].nxt = head[u];
    e[tot].to = v;
    head[u] = tot;
}

void solve1(int x, int fa, int k) // 1 操作
{
    k += down[x]; // 因为是 dfs,所以下面遍历的都是子树,全部需要加 k
    num[x] += k; 
    for (int i = head[x]; i; i = e[i].nxt) // 向下遍历
    {
        int v = e[i].to;
        if (v == fa) // 不能往上走
            continue;
        solve1(v, x, k);
    }
    return;
}

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> num[i];
    for (int i = 1; i < n; i++)
    {
        int u, v;
        cin >> u >> v;
        addedge(u, v);
        addedge(v, u);
    }
    cin >> m;
    for (int i = 1; i <= m; i++) // 先预处理一下,我们上面说的第一步
    {
        int op, x, y;
        cin >> op >> x >> y;
        if (op == 1)
            down[x] += y;
        if (op == 2)
            near[x] += y;
    }
    solve1(1, 0, 0); // 1 操作
    for (int i = 1; i <= n; i++) // 2 操作
    {
        num[i] += near[i];
        for (int j = head[i]; j; j = e[j].nxt) // 枚举与 i 相邻的点
        {
            int v = e[j].to;
            num[v] += near[i];
        }
    }
    cin >> q;
    for (int i = 1; i <= q; i++) // 问哪个输出哪个
    {
        int id;
        cin >> id;
        cout << num[id] << '\n';
    }
    return 0;
}

[CSP-J2022 山东] 吟诗

题目背景

受疫情影响,山东省取消了 CSP-J 2022 认证活动,并于次年三月重新命题,在省内补办比赛。

题目描述

“文章本天成,妙手偶得之。”

吟诗是表达情怀的常用手段,战争落下了帷幕,常年的军旅生活使得小虾米喜欢上了豪放派的诗歌。

这一天,小虾米突然想吟诗了。著名的豪放派诗人苏轼有“老夫聊发少年狂,左牵黄,右擎苍。”的豪放,又有“十年生死两茫茫,不思量,自难忘。”的悲怆。小虾米心向往之,于是也想用《江城子》词牌名作诗。

小虾米想作出能流传千古的诗,根据经验,如果一首诗存在妙手就能流传千古。

具体来说,一首 N 个字的诗,每个字可以用 111101010 之间的某个正整数来表示。同时存在三个正整数 X,Y,Z(1≤X≤7,1≤Y≤5,1≤Z≤5)X,Y,Z\left(1\le X\le7,1\le Y\le5,1\le Z\le5\right)X,Y,Z(1X7,1Y5,1Z5),如果诗中出现了三个连续的片段使得第一个片段之和为 XXX,第二个片段之和为 YYY,第三个片段之和为 ZZZ,则小虾米认为这首诗出现了妙手

即长度为 nnn 的序列 a1,a2,…an(1≤ai≤10)a_{1},a_{2},\dots a_{n} \left(1\le a_{i}\le10\right)a1,a2,an(1ai10),如果存在 i,j,k,l(1≤i<j<k<l≤n)i,j,k,l\left(1\le i<j<k<l\le n\right)i,j,k,l(1i<j<k<ln) 使得ai+ai+1+…aj−1=Xa_{i}+a_{i+1}+\dots a_{j-1}=Xai+ai+1+aj1=Xaj+aj+1+…ak−1=Ya_{j}+a_{j+1}+\dots a_{k-1}=Yaj+aj+1+ak1=Yak+ak+1+…al−1=Za_{k}+a_{k+1}+\dots a_{l-1}=Zak+ak+1+al1=Z 同时成立,则认为序列出现了妙手(注:第二个片段紧接第一个片段,第三个片段紧接第二个片段)。

举例来说,如果 N=7N=7N=7X=7X=7X=7Y=3Y=3Y=3Z=3Z=3Z=3,则所有长度为 777 的序列中,很显然共有 10710^{7}107 种序列,其中一种序列 [1,5,2,2,1,3,4]\left[1,5,2,2,1,3,4\right][1,5,2,2,1,3,4] 出现了妙手,因为存在三个连续的区间 [2,3]\left[2,3\right][2,3][4,5]\left[4,5\right][4,5][6,6]\left[6,6\right][6,6] 满足它们的和分别为 X=7X=7X=7Y=3Y=3Y=3Z=3Z=3Z=3

小虾米想知道在给定 N,X,Y,ZN,X,Y,ZN,X,Y,Z 的前提下(共计 10n10^{n}10n 种序列,即共 10n10^{n}10n 种诗),计算有多少种存在妙手的诗,请你帮他计算出答案。

由于答案可能很大,请你将结果对 998244353998244353998244353 取模。

输入格式

一行,以空格隔开的 4 个正整数 N,X,Y,ZN,X,Y,ZN,X,Y,Z,分别表示序列长度和题目中 X,Y,ZX,Y,ZX,Y,Z 的值。

输出格式

一行,一个整数,表示答案对 998244353998244353998244353 取模的结果。

输入输出样例 #1

输入 #1

3 2 3 3

输出 #1

1

输入输出样例 #2

输入 #2

4 7 5 5

输出 #2

34

输入输出样例 #3

输入 #3

23 7 3 5

输出 #3

824896638

说明/提示

样例一说明

在所有可能的序列中,只能构造出一种序列 [2,3,3]\left[2,3,3\right][2,3,3] 满足题意,因此答案为 111

数据范围

对于 30%30\%30% 的数据,3≤N≤53\le N\le53N5

对于 60%60\%60% 的数据,3≤N≤203\le N\le203N20

对于 100%100\%100% 的数据,3≤N≤40,1≤X≤7,1≤Y≤5,1≤Z≤53\le N\le40,1\le X\le7,1\le Y\le5,1\le Z\le53N40,1X7,1Y5,1Z5

解题思路

这一把还没考 dpdpdp 呢,所以肯定是 dpdpdp(狗头)。

这一题比较有防 AK 的意思哈,观察数据范围,有可能是一个状态压缩 dpdpdp(实际上就是)。首先我们需要明确一点,计算满足条件的序列数量其实是不太好整的,因为一个序列里面可能出现好几个 “妙手”,那咋办呢?你计算不满足条件的序列数量不就行了嘛,然后拿全部的数量减一下就是答案。

然后我们设计状态 dp[i][j]dp[i][j]dp[i][j],其中 iii 表示前 iii 个数字,jjj 表示一个压缩的状态。我们来详细说一下 jjjjjj 是一个二进制数,表示的是前 iii 个数有哪些后缀和。例如:序列 [1,2,3,4][1, 2, 3, 4][1,2,3,4],有 4、7、9、104、7、9、1047910(4,4 + 3,4 + 3 + 2,4 + 3 + 2 + 1) 一共 4 种后缀和,那么就让 j=1101001000j = 1101001000j=1101001000(二进制,最右边一位表示后缀和 1 在不在)。那么 dp[i][j]dp[i][j]dp[i][j] 就是序列的前 iii 个数,后缀和满足 jjj 有多少种可能(只是前 iii 个数字)。

那么怎么判断是不是我们想要的序列呢?假设 X=3,Y=2,Z=1X = 3,Y = 2,Z = 1X=3Y=2Z=1,如果在 dpdpdp 的时候,发现同时存在后缀和 1、3、61、3、6136 的话,是不是就能说明存在一个 “妙手” 呢?如果遇到这种情况我们不统计就可以了。

最后我们讨论一下如何进行状态转移,显然我们应该按照 iii 从小到大的顺序进行转移,那么假设当前我们枚举到了序列的前 iii 个,再枚举这一位是几,假设是 kkk,然后第三个数,我们枚举前 i−1i - 1i1 个数后缀和的情况,令其为 jjj。因为我们加上了一个 kkk,所以之前的后缀和都应该加上 kkk,等价于 jjj 左移 kkk 位,又因为 kkk 本身也算一个后缀和,所以再让 jjj 或上一个 (1<<(k−1))(1 << (k - 1))(1<<(k1)) 表示 kkk 这个后缀和也存在,得到的值假设为 sss,那么就有了下面的转移方程。
s=((j<<k)∣(1<<(k−1)))s = ((j << k) | (1 << (k - 1)))s=((j<<k)(1<<(k1)))
dp[i][s]=dp[i][s]+dp[i−1][j]dp[i][s] = dp[i][s] + dp[i - 1][j]dp[i][s]=dp[i][s]+dp[i1][j]

因为 jjj 不需要储存大于 (X+Y+Z)(X+Y+Z)(X+Y+Z) 的后缀和(没意义,不能帮助我们确定是否存在 “妙手”),所以我们要保证 j≤(1<<(x+y+z))−1j \le (1 << (x + y + z)) - 1j(1<<(x+y+z))1

#include <bits/stdc++.h>

using namespace std;

const int maxn = 45, MOD = 998244353;
int n, x, y, z, dp[maxn][1 << 18];

int main()
{
    long long ans = 1;
    cin >> n >> x >> y >> z;
    int all = (1 << (x + y + z)) - 1, a = ((1 << (z - 1)) | (1 << (y + z - 1)) | (1 << (x + y + z - 1))); // 要保证压缩的状态小于等于 all,大家注意 a 的含义,如果一个状态包含 a,说明存在妙手
    dp[0][0] = 1; // 初始状态
    for (int i = 1; i <= n; i++) // 前 i 个数
    {
        ans = ans * 10 % MOD; // 所有的状态数,顺道求了
        for (int j = 0; j <= all; j++) // 前 i - 1 个数的后缀和状态
            for (int k = 1; k <= 10; k++) // 当前这个数是多少
            {
                int s = ((j << k) | (1 << (k - 1))) & all; // &all 是为了保证去掉溢出的内容,让状态值小于 all
                if ((s | a) != s) // 说明 s 是一个不存在妙手的状态
                    dp[i][s] = (dp[i][s] + dp[i - 1][j]) % MOD; // 记得取模
            }
    }
    for (int i = 0; i <= all; i++) // 去掉所有不合法的状态
        ans = (ans + MOD - dp[n][i]) % MOD; // +MOD 防止 ans 被减成负数
    cout << ans;
    return 0;
}










本人能力有限,如有不当之处敬请指教!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值