2月3日模拟赛分析及总结

本次模拟赛4道都是好题,第一题【蛇形矩阵】考查了数学知识,第二题【乌龟棋】是一道经典\(DP\)题,第三题【菜肴制作】是拓扑排序的模板题,第四题【拜年】为 \(Floyd\)

整体难度为黄到绿

然而本人成功地考出了\(100 + 0 + 100 + 5\) 的好成绩,在此总结一下原因:
1. \(DP\)题几乎没做(才\(13\)道)
2. \(Floyd\)就变了下形就不注意细节
下次考模板,争取 \(300+\)\(\cdots\cdots\)


Problem A: 蛇形矩阵

题目描述

给定一个正整数 \(n\),现在构造一个 \(n \times n\) 的蛇形矩阵,矩阵每个格子内填入一个数字。矩阵右上角填入1,左下角填入 \(n \times n\),从 \(1 \dots n \times n\) 依次填入数字的顺序为 \((1,n)\to(1,n-1)\to(2,n)\to(3,n)\to(2,n-1)\to(1,n-2)\to\dots\to(n,n)\)。譬如说 \(4 \times 4\) 的蛇形矩阵是:

7 6 2 1
13 8 5 3
14 12 9 4
16 15 11 10

现在给出 \(n\),求 \(n \times n\) 的蛇形矩阵中第 \(x\)\(y\) 列的数字是什么。

输入格式

一行,三个整数 \(n,x,y\)

输出格式

一行,一个整数,表示 \(n \times n\) 的蛇形矩阵中第 \(x\)\(y\) 列的数字。

输入输出样例

输入 #1

4 3 1

输出 #1

14

数据范围

\(40\%\) 的数据,\(n\le100\)

\(100\%\) 的数据,\(n\le30000\)

思路

一道4年级数学题,记住一个就行:行数 \(+\) 列数 \(-1=\) 斜线数,前 \(n\) 条斜线上共有 \(n\times(n+1)\div2\) 个数。

代码

#include <iostream>
#define int long long
using namespace std;
 
signed main()
{
    int n, x, y;
    cin >> n >> x >> y;
    int s = n - y + 1;
    int a = x + s - 1;
    if (a <= n)
    {
        int b = a - 1;
        if (b % 2 == 0)
        {
            cout << b * (b + 1) / 2 + s;
        }
        else
        {
            cout << b * (b + 1) / 2 + x;
        }
    }
    else
    {
        int sum = n * n + 1;
        int l = n - x + 1;
        int m = y + l - 2;
        int k;
        if (m % 2 == 0)
        {
            k = m * (m + 1) / 2 + y;
        }
        else
        {
            k = m * (m + 1) / 2 + l;
        }
        cout << sum - k;
    }
    return 0;
}
/**************************************************************
    Language: C++
    Result: Accepted
    Time:0 ms
    Memory:2020 kb
****************************************************************/

Problem B: 乌龟棋(P1541 [NOIP2010 提高组] 乌龟棋

题目背景

小明过生日的时候,爸爸送给他一副乌龟棋当作礼物。

题目描述

乌龟棋的棋盘是一行 \(N\) 个格子,每个格子上一个分数(非负整数)。棋盘第 \(1\) 格是唯一的起点,第 \(N\) 格是终点,游戏要求玩家控制一个乌龟棋子从起点出发走到终点。

乌龟棋中 \(M\) 张爬行卡片,分成 \(4\) 种不同的类型( \(M\) 张卡片中不一定包含所有 \(4\) 种类型的卡片,见样例),每种类型的卡片上分别标有 \(1,2,3,4\) 四个数字之一,表示使用这种卡片后,乌龟棋子将向前爬行相应的格子数。游戏中,玩家每次需要从所有的爬行卡片中选择一张之前没有使用过的爬行卡片,控制乌龟棋子前进相应的格子数,每张卡片只能使用一次。

游戏中,乌龟棋子自动获得起点格子的分数,并且在后续的爬行中每到达一个格子,就得到该格子相应的分数。玩家最终游戏得分就是乌龟棋子从起点到终点过程中到过的所有格子的分数总和。

很明显,用不同的爬行卡片使用顺序会使得最终游戏的得分不同,小明想要找到一种卡片使用顺序使得最终游戏得分最多。

现在,告诉你棋盘上每个格子的分数和所有的爬行卡片,你能告诉小明,他最多能得到多少分吗?

输入格式

每行中两个数之间用一个空格隔开。

\(1\)\(2\) 个正整数 \(N,M\),分别表示棋盘格子数和爬行卡片数。

\(2\)\(N\) 个非负整数,\(a_1,a_2,\dots,a_N\),其中 \(a_i\) 表示棋盘第 \(i\) 个格子上的分数。

\(3\)\(M\) 个整数,\(b_1,b_2,\dots,b_M\),表示 \(M\) 张爬行卡片上的数字。

输入数据保证到达终点时刚好用光 \(M\) 张爬行卡片。

输出格式

\(1\) 个整数,表示小明最多能得到的分数。

输入输出样例

输入 #1

9 5

6 10 14 2 8 8 18 5 17

1 3 1 2 1

输出 #1

73

输入 #2

13 8

4 96 10 64 55 13 94 53 5 24 89 8 30

1 1 1 1 1 2 4 1

输出 #2

455

输入输出样例 1 说明

小明使用爬行卡片顺序为 \(1\)\(1\)\(3\)\(1\)\(2\),得到的分数为

\(6+10+14+8+18+17=73\)。注意,

由于起点是 \(1\),所以自动获得第 \(1\) 格的分数 \(6\)

数据范围

对于 \(30\%\) 的数据有 \(1 \le N \le 30\)\(1 \le M \le 12\)

对于 \(50\%\) 的数据有 \(1 \le N \le 120\)\(1 \le M \le 50\),且 \(4\) 种爬行卡片,每种卡片的张数不会超过 \(20\)

对于 \(100\%\) 的数据有 \(1 \le N \le 350\)\(1 \le M \le 120\),且 \(4\) 种爬行卡片,每种卡片的张数不会超过 \(40\)\(0 \le a_i \le 100\)\(1 \le i \le N\)\(1 \le b_i \le 4\)\(1 \le i \le M\)

思路

数据范围太水,不用状压\(DP\)(我也不会)

整个四维\(DP\)就行了

状态转移方程:\(dp[i][j][k][l] = max(dp[i][j][k][l],dp[i-1][j][k][l]+\)当前卡片的分数\(,dp[i][j-1][k][l]+\)当前卡片的分数\(,dp[i][j][k-1][l]+\)当前卡片的分数\(,dp[i][j][k][l-1]+\)当前卡片的分数\()\)

代码(知道您们只看这个)

#include <iostream>
using namespace std;
 
int n, m;
int a[355], b[10], dp[45][45][45][45];
 
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; ++i)
    {
        cin >> a[i];
    }
    for (int i = 1; i <= m; ++i)
    {
        int tmp;
        cin >> tmp;
        ++b[tmp];
    }
    dp[0][0][0][0] = a[1];
    for (int i = 0; i <= b[1]; ++i)
    {
        for (int j = 0; j <= b[2]; ++j)
        {
            for (int k = 0; k <= b[3]; ++k)
            {
                for (int l = 0; l <= b[4]; ++l)
                {
                    int t = i + j * 2 + k * 3 + l * 4 + 1;
                    if (i)
                    {
                        dp[i][j][k][l] = max(dp[i][j][k][l], dp[i-1][j][k][l] + a[t]);
                    }
                    if (j)
                    {
                        dp[i][j][k][l] = max(dp[i][j][k][l], dp[i][j-1][k][l] + a[t]);
                    }
                    if (k)
                    {
                        dp[i][j][k][l] = max(dp[i][j][k][l], dp[i][j][k-1][l] + a[t]);
                    }
                    if (l)
                    {
                        dp[i][j][k][l] = max(dp[i][j][k][l], dp[i][j][k][l-1] + a[t]);
                    }
                }
            }
        }
    }
    cout << dp[b[1]][b[2]][b[3]][b[4]];
    return 0;
}
/**************************************************************
    Language: C++
    Result: Accepted
    Time:12 ms
    Memory:18044 kb
****************************************************************/

Problem C: 菜肴制作(P3243 [HNOI2015]菜肴制作

题目描述

知名美食家小 \(A\) 被邀请至 \(ATM\) 大酒店,为其品评菜肴。 \( ATM\) 酒店为小 \(A\) 准备了 \(N\) 道菜肴,酒店按照为菜肴预估的质量从高到低给予 \(1\)\(N\) 的顺序编号,预估质量最高的菜肴编号为 \(1\)。由于菜肴之间口味搭配的问题,某些菜肴必须在另一些菜肴之前制作,具体的,一共有 \(M\) 条形如“ \(i\) 号菜肴‘必须’先于 \(j\) 号菜肴制作”的限制,我们将这样的限制简写为 \(<i,j>\)。现在,酒店希望能求出一个最优的菜肴的制作顺序,使得小 \(A\) 能尽量先吃到质量高的菜肴:也就是说,\((1)\)在满足所有限制的前提下,\(1\) 号菜肴“尽量”优先制作;\((2)\)在满足所有限制,\(1\) 号菜肴“尽量”优先制作的前提下,\(2\) 号菜肴“尽量”优先制作;\((3)\) 在满足所有限制,\(1\) 号和 \(2\) 号菜肴“尽量”优先的前提下,\(3\) 号菜肴“尽量”优先制作;\((4)\) 在满足所有限制,\(1\) 号和 \(2\) 号和 \(3\) 号菜肴“尽量”优先的前提下,\(4\) 号菜肴“尽量”优先制作;\((5)\)以此类推。
\(1\):共 \(4\) 道菜肴,两条限制 \(<3,1>\)\(<4,1>\),那么制作顺序是 \(3,4,1,2\)

\(2\):共 \(5\) 道菜肴,两条限制 \(<5,2>\)\(<4,3>\),那么制作顺序是 \(1,5,2,4,3\)

\(1\) 里,首先考虑 \(1\),因为有限制 \(<3,1>\)\(<4,1>\),所以只有制作完 \(3\)\(4\) 后才能制作 \(1\),而根据 \((3)\)\(3\) 号又应“尽量”比 \(4\) 号优先,所以当前可确定前三道菜的制作顺序是 \(3,4,1\);接下来考虑 \(2\),确定最终的制作顺序是 \(3,4,1,2\)

\(2\) 里,首先制作 \(1\) 是不违背限制的;接下来考虑 \(2\) 时有 \(<5,2>\) 的限制,所以接下来先制作 \(5\) 再制作 \(2\);接下来考虑 \(3\) 时有 \(<4,3>\) 的限制,所以接下来先制作 \(4\) 再制作 \(3\),从而最终的顺序是 \(1,5,2,4,3\)
现在你需要求出这个最优的菜肴制作顺序。无解输出 “\(\rm Impossible!\)”(不含引号,首字母大写,其余字母小写)

输入格式

第一行是一个正整数 \(D\),表示数据组数。
接下来是 \(D\) 组数据。
对于每组数据:
第一行两个用空格分开的正整数 \(N\)\(M\),分别表示菜肴数目和制作顺序限制的条目数。
接下来 \(M\) 行,每行两个正整数 \(x,y\),表示“\(x\) 号菜肴必须先于 \(y\) 号菜肴制作”的限制。(注意:\(M\) 条限制中可能存在完全相同的限制)

输出格式

输出文件仅包含 \(D\) 行,每行 \(N\) 个整数,表示最优的菜肴制作顺序,或者“\(\rm Impossible!\)”表示无解(不含引号)。

输入输出样例

输入 #1

3

5 4

5 4

5 3

4 2

3 2

3 3

1 2

2 3

3 1

5 2

5 2

4 3

输出 #1

1 5 3 4 2

Impossible!

1 5 2 4 3

样例解释

第二组数据同时要求菜肴 \(1\) 先于菜肴 \(2\) 制作,菜肴 \(2\) 先于菜肴 \(3\) 制作,菜肴 \(3\) 先于菜肴 \(1\) 制作,而这是无论如何也不可能满足的,从而导致无解。

数据范围

\(100\%\) 的数据满足 \(NM \le 100000,D \le 3\)

思路

这题就是个拓扑排序的板子。

\({\color{Red}\colorbox{White}{求字典序最小的排列是错的!!!}}\)

可以举出反例:\(4\)种菜肴,限制为 \(<2,4><3,1><2,4><3,1>\)
那么字典序最小的是 \(2,3,1,4\),但题目要求的最优解是 \(3,1,2,4\)

继续考虑,可以发现,如果最后一个数字尽可能大,那么这样是符合要求的,
因为如果设最后一个数字是 \(a\),那么除了 \(a\) 之外的所有数都不会被放到最后一个位置,而这样就可以让前面所有小于 \(a\) 的数都尽量靠前,达到题目的目标。

因此,最优解就是符合条件的排列中,反序列的字典序最大的排列。

所以,在反图上跑拓扑排序,求最大字典序。在实现上,由于需要多次找出队列
中的最大值,因此用优先队列。

代码

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
 
const int MAXN = 100005;
const int MAXM = 100005;
 
int d, n, m, k;
int cnt, head[MAXN], in[MAXN], ans[MAXN];
 
struct edge
{
    int to, nxt;
}e[MAXM];
 
void init()
{
    k = cnt = 0;
    memset(head, 0, sizeof(head));
    memset(in, 0, sizeof(in));
    for (int i = 0; i < MAXM; ++i)
    {
        e[i] = {};
    }
}
 
void add(int u, int v)
{
    e[++cnt] = {v, head[u]};
    head[u] = cnt;
}
 
void topo()
{
    priority_queue <int> pq;
    for (int i = 1; i <= n; ++i)
    {
        if (!in[i])
        {
            pq.push(i);
        }
    }
    while (!pq.empty())
    {
        int cu = pq.top();
        pq.pop();
        ++k;
        ans[k] = cu;
        for (int i = head[cu]; i; i = e[i].nxt)
        {
            int cv = e[i].to;
            if (--in[cv] == 0)
            {
                pq.push(cv);
            }
        }
    }
}
 
int main()
{
    cin >> d;
    while (d--)
    {
        init();
        cin >> n >> m;
        for (int i = 1; i <= m; ++i)
        {
            int u, v;
            cin >> u >> v;
            add(v, u);
            ++in[u];
        }
        topo();
        if (k < n)
        {
            cout << "Impossible!";
        }
        else
        {
            for (int i = n; i; --i)
            {
                cout << ans[i] << " ";
            }
        }
        cout << endl;
    }
    return 0;
}
/**************************************************************
    Language: C++
    Result: Accepted
    Time:260 ms
    Memory:4564 kb
****************************************************************/

Problem D: 拜年

题目描述

春节临近,小林同学被爸爸妈妈带着四处拜年(疫情期间不建议聚集哦0)。
为了避免感染病毒,今年他们片区的人做出了这样的决策:
拜访者和和被拜访者以及拜年经过的路径上全部人家都做了核酸检测之后拜年者才能去拜年。
现在给你这个片区的地图,求每个拜访者拜年的最短路径长度。

输入格式

第一行是 \(n\)\(m\)\(q\)。分别代表这个片区有 \(n\) 户人,这 \(n\) 户人家之间有 \(m\) 条路,\(q\) 代表操作次数。

接下来 \(m\) 行 每行有3个数 \(u\)\(v\)\(w\)。代表编号 \(u\) 的人家和编号 \(v\) 的人家之间存在一条长度为 \(w(w \le 0)\)的单向的路。(存在重边和自环)

接下来 \(q\) 行是操作共有两种情况:

  • 输入的第一个数 \(op\)\(0\) 时,第二个数是 \(x\),代表着编号 \(x\) 的这户人家去做了核酸检测,并且结果阴性。
  • 输入的第一个数 \(op\)\(1\) 时,接下来输入两个数 \(x\)\(y\),代表着编号 \(x\) 的这户人家想去 \(y\) 号家里拜年。

输出格式

对每次 \(op\) 做如下操作:

  • \(op\)\(0\) 的情况:

①如果 \(x\) 这户人家没做检测,则检测这户人家。此行不需要输出。

②如果 \(x\) 这户人家已经做了核酸检测,输出“\(ERROR!\) \(At\) \(point\) \(x\)

  • \(op\)\(1\) 的情况:

①如果 \(x\)\(y\) 这两户人家有任何一户没有做检测,输出“\(ERROR!\) \(At\) \(path\) \(x\) \(to\) \(y\)

②如果 \(x\)\(y\) 这两户人家之间没有一条符合条件的路径,输出“\(No\) \(such\) \(path\)

③如果 \(x\) 能去 \(y\) 家拜年,输出 \(x\)\(y\) 的最短路。

输入输出样例

输入 #1

5 10 10

1 2 6335

0 4 5725

3 3 6963

4 0 8146

1 2 9962

1 0 1943

2 1 2392

4 2 154

2 2 7422

1 3 9896

0 1

0 3

0 2

0 4

0 4

0 1

1 3 3

1 1 1

0 3

0 4

输出 #1

ERROR! At point 4

ERROR! At point 1

0

0

ERROR! At point 3

ERROR! At point 4

数据范围

对于 \(20\%\) 的数据,保证 \(n \le 10,m \le 100,q \le 100\)

对于 \(50\%\) 的数据,保证 \(n \le 50,m \le 5000,q \le 5000\)

对于 \(80\%\) 的数据,保证 \(n \le 200,m \le 50000,q \le 50000\)

对于全部的数据,保证 \(n \le 300,m \le 100000,q \le 100000\)

思路

多加一个 \(check\) 数组记录是否做过核酸检测

\({\color{Red}\colorbox{White}{关键:每次某户人家(不妨设为 x)去做过核酸检测都要跑一遍Floyd}}\),否则只有 \(5\) 分(明白我怎么 \(5\) 分了吧)

一个新的问题:每次重跑 \(Floyd\),时间复杂度最坏能到 \(O(n^3q)\),肯定炸了

蒙猜测量仔细推导后不难得出:只有以 \(x\) 为途经点的 \(dis\) 值才有可能被更新,因此每次做过核酸检测后把 \(x\) 作为参数传进 \(Floyd\) 函数,里面跑双重循环即可,时间复杂度为 \(O(n^2p)\)

\({\color{Green}\colorbox{White}{~~好像还是炸了~~}}\),但提交上去 \(A\) 了,估计数据较水

还是上代码吧,有哪位大佬奆学有更优解麻烦下方评论呗(打表卡常除外,重点提防wjy同学手动滑稽\(Orz\) \(stO\)

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
 
const int INF = 0x3f3f3f3f;
const int MAXN = 305;
 
int n, m, q;
int dis[MAXN][MAXN], check[MAXN];
 
void floyd(int k)
{
    for (int i = 0; i < n; ++i)
    {
        for (int j = 0; j < n; ++j)
        {
            dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
        }
    }
}
 
int main()
{
    scanf("%d%d%d", &n, &m, &q);
    memset(dis, INF, sizeof(dis));
    for (int i = 0; i < n; ++i)
    {
        dis[i][i] = 0;
    }
    for (int i = 1; i <= m; ++i)
    {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        dis[u][v] = min(dis[u][v], w);
    }
    for (int i = 1; i <= q; ++i)
    {
        int op, x, y;
        scanf("%d", &op);
        if (!op)
        {
            scanf("%d", &x);
            if (check[x])
            {
                printf("ERROR! At point %d\n", x);
            }
            else
            {
                check[x] = 1;
                floyd(x);
            }
        }
        else
        {
            scanf("%d%d", &x, &y);
            if (check[x] && check[y])
            {
                if (dis[x][y] == INF)
                {
                    printf("No such path\n");
                }
                else
                {
                    printf("%d\n", dis[x][y]);
                }
            }
            else
            {
                printf("ERROR! At path %d to %d\n", x, y);
            }
        }
    }
    return 0;
}
/**************************************************************
    Language: C++
    Result: Accepted
    Time:36 ms
    Memory:2388 kb
****************************************************************/
posted @ 2021-08-07 17:44  mango09  阅读(72)  评论(0编辑  收藏  举报
-->