CSP-J2020题解

说明

本文章转载至【零一原创】2020 CSP-J 复赛题解 400分攻略 - 代码先锋网,因为我太菜了不会做2020的题解(我还是个小学生)。

优秀的拆分(power)

【题目描述】
般来说,一个正整数可以拆分成若干个正整数的和。例如,1=1,10=1+2+3+4等。对于正整数n的一种特定拆分,我们称它为“优秀的",当且仅当在这种拆分下,n被分解为了若干个丕同的2的正整数次幂。注意,一个数x能被表示成2的正整数次幂,当且仅当x能通过正整数个2相乘在一起得到。
例如,10=8+2=23+21是一个优秀的拆分。但是,7=4+2+1=2+21+20就不是一个优秀的拆分,因为1不是2的正整数次幂。现在,给定正整数n,你需要判断这个数的所有拆分中,是否存在优秀的拆分。若存在,请你给出具体的拆分方案。
【输入格式】
输入文件名为 power.in.
输入文件只有一行,一个正整数n,代表需要判断的数。
【输出格式】
输出文件名为power.out.
如果这个数的所有拆分中,存在优秀的拆分。那么,你需要从大到小输出这个拆分中的每一个数,相邻两个数之间用一个空格隔开。可以证明,在规定了拆分数字的顺序后,该拆分方案是唯一的。若不存在优秀的拆分,输出"-1"(不包含双引号)。
【样例1输入】
6
【样例1输出】
4 2
【样例1解释】
6=4+2=22+21是一个优秀的拆分。注意,6=2+2+2不是一个优秀的拆分,因为拆分成的3个数不满足每个数互不相同。
【样例2输入】
7
【样例2输出】
-1
【样例3】
见选手目录下的power/power3.in与power/power3.ans
【数据范围与提示】
对于20%的数据,n<=10.
对于另外20%的数据,保证n为奇数。
对于另外20%的数据,保证n为2的正整数次幂。
对于80%的数据,n<=1024.
对于100%的数据,1<=n<=1x10^7.

题目解析:

一个数本来就可以拆分成2的正整数次幂, 因为利用它的二进制即可得到。例如:6的二进制是 1 1 0,分别代表22,21,20
所以6可以看成22+21=4+2。对n进行二进制分解,然后倒序输出即可。参考程序:

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int n;
int main()
{
    cin >> n;
    if (n % 2 == 1) {
        cout << -1;
        return 0;
    }
    if (n <=10) {
        int a[30], cnt = 0;
        int base = 1;
        while (n) {
            if (n % 2 == 1) {
                a[++cnt] = base;
            }
            n /= 2;
            base *= 2;
        }
        for (int i = cnt; i >= 1; i --) {
            cout << a[i] << " ";
        }
        return 0;
    }
    cout << n;
    return 0;
}

零一点评:

本题难度显著高于2019,2018,2017年的第一题。没有联想到二进制转化的同学可能会被卡住。一旦联想到,就是一个【入门】级题目。

骗分方法:
第一个20%:对每个n,打表
第二个20%:根据题意,不存在,直接输出-1
第三个20%:它的拆分就是它本身,直接输出n本身
【数据范围与提示】
对于20%的数据,n<=10.
对于另外20%的数据,保证n为奇数。
对于另外20%的数据,保证n为2的正整数次幂。
对于80%的数据,n<=1024.
对于100%的数据,1<=n<=1x10^7.

第二题 直播获奖(live)

【题目描述】
NO12130即将举行。为了增加观赏性,CCF决定逐一评出每个选手的成绩,并直播即时的获奖分数线。本次竞赛的获奖率为w%,即当前排名前w%的选手的最低成绩就是即时的分数线。
更具体地,若当前已评出了p个选手的成绩,则当前计划获奖人数为max(1,[p*w%]),其中w是获奖百分比,[x]表示对x向下取整max(x,y)表示x和y中较大的数。如有选手成绩相同,则所有成绩并列的选手都能获奖,因此实际获奖人数可能比计划中多。
作为评测组的技术人员,请你帮CCF写一个直播程序。
【输入格式】
输入文件名为live.in.
第1行两个正整数n,w,分别代表选手总数与获奖率。
第2行有n个非负整数,依次代表逐一评出的选手成绩。
【输出格式】
输出文件名为live.out.
只有一行,包含n个非负整数,依次代表选手成绩逐一评出后,即时的获奖分数线。相邻两个整数间用一个空格分隔。
【样例1输入】
10 60
200 300 400 500 600 600 0 300 200 100
【样例1输出】
200 300 400 400 400 500 400 400 300 300
【数据范围与提示】
对于20%的数据,n<=10
对于另外20%的数据,保证n为奇数。
对于另外20%的数据,保证n为2的正整数次幂。
对于80%的数据,n<=1024
对于100%的数据,1<=n<=10^7
【数据范围与提示】
测试点编号 n
1~3 =10
4~6 =500
7~10 =2000
11~17 =10000
18~20 =100000
对于所有测试点,每个选手的成绩均为不超过600的非负整数,获奖百分比w是一个正整数目1<=w<=99.
在计算计划获奖人数时,如用浮点类型的变量(如C/C++中的float double,Pascal中的real,double,extended等)存储获奖比例w%,则计算5x60%时的结果可能为3.000001,也可能为2.999999,向下取整后的结果不确定。因此,建议仅使用整型变量,以计算出准确值。

题目解析:

50分程序:对于每个选手,把之前的数据进行sort排序,选择max(1, ⌊𝑝 × 𝑤%⌋) 人处的分数。时间复杂度约为O(n2)。

100分程序:观察到数据范围里成绩在600以内,因此可以变sort排序为桶排序。准备600个桶,每个选手处,把他分数对应的桶里的个数++。然后从第600号桶倒序循环到1号桶,进行人数统计,输出max(1, ⌊𝑝 × 𝑤%⌋) 人处的分数。时间复杂度为O(600n)。

零一点评:

本题延续了2019年第二题的风格:除了基础代码能力,增加了对时间复杂度的考察。如果时间复杂度过关,那么难度要弱于2019年的公交换乘。另外本题对于浮点数运算的考察也是一个易错点。

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int n, w, b[605];
int main()
{
    cin >> n >> w;
    for (int i = 1; i <= n; i ++) {
        int v;
        cin >> v;
        b[v]++;
        int cnt = max(1, i * w / 100);
        int k = 600, sum = 0;
        while (k >= 0) {
            if (sum + b[k] < cnt) {
                sum += b[k];
                k --;
            } else {
                cout << k << " ";
                break;
            }
        }
    }
    return 0;
}

第三题 表达式(expr)

【题目描述】
小C热衷于学习数理逻辑。有一天,他发现了一种特别的逻辑表达式。在这种逻辑表达式中,所有操作数都是变量,且它们的取值只能为0或1,运算从左往右进行。如果表达式中有括号,则先计算括号内的子表达式的值。特别的,这种表达式有且仅有以下几种运算:
1,与运算:a&b,当且仅当a和b的值都为1时,该表达式的值为1,其余情况该表达式的值为0.
2,或运算:a|b,当且仅当a和b的值都为0时,该表达式的值为0,其余情况该表达式的值为1.
3,取反运算:!a。当且仅当a的值为0时,该表达式的值为1,其余情况该表达式的值为0.
小C想知道,给定一个逻辑表达式和其中每一个操作数的初始取值后,再取反某一个操作数的值时,原表达式的值为多少。
为了化简对表达式的处理,我们有如下约定:
表达式将采用后缀表达式的方式输入。后缀表达式的定义如下:
1,如果E是一个操作数,则E的后缀表达式是它本身。
2,如果E是E1 op E2形式的表达式,其中op是任何二元操作符,且优先级不高于E1、E2中括号外的操作符,则E的后缀式为E1 E2 op,其中E1、E2分别为E1、E2的后缀式。
3,如果E是(E1)形式的表达式,则E1的后缀式就是E的后缀式。
同时为了方便,输入中:
a)与运算符(&)、或运算符(|)、取反运算符(!)的左右均有一个空格,但表达式末尾没有空格。
b)操作数由小写字母x与一个正整数拼接而成,正整数表示这个变量的下标。例如:x10,表示下标为10的变量x10。数据保证每个变量在表达式中出现恰好一次。
【输入格式】
输入文件名为expr.in.
第一行包含一个字符串s,表示上文描述的表达式。
第二行包含一个正整数n,表示表达式中变量的数量。表达式中变量的下标为1,2,…,n
第三行包含n个整数,第i个整数表示变量xi的初值。
第四行包含一个正整数q,表示询问的个数。
接下来q行,每行一个正整数,表示需要取反的变量的下标。注意,每一个询问的修改都是临时的,即之前询问中的修改不会对后续的询问造成影响。
数据保证输入的表达式合法。变量的初值为0或1.
【输出格式】
输出文件名为expr.out.
输出一共有q行,每行一个0或1,表示该询问下表达式的值。
【样例1输入】
x1 x2 & x3 |
3
1 0 1
3
1
2
3
【样例1输出】
1
1
0
【样例1解释】
该后缀表达式的中缀表达式形式为(x1 & x2) | x3
对于第一次询问,将x1的值取反。此时,三个操作数对应的赋值依次为0,0,1,原表达式的值为(0 & 0) | 1 = 1.
对于第二次询问,将x2的值取反。此时,三个操作数对应的赋值依次为1,1,1,原表达式的值为(1&1) |1 = 1.
对于第三次询问,将x3的值取反。此时,三个操作数对应的赋值依次为1,0,0,原表达式的值为(1& 0) | 0 = 0.
【样例2输入】
x1 ! x2 x4 | x3 x5 ! & & ! &
5
0 1 0 1 1
3
1
3
5
【样例2输出】
0
1
1
【样例2解释】
该表达式的中缀表达式形式为(! x1)&(!((x2 | x4)&(x3 &(! x5))))。
【样例3】
见选手目录下的exprlexpr3.in与expr/expr3.ans.
【数据范围与提示】
对于20%的数据,表达式中有且仅有与运算(&)或者或运算(|)
对于另外30%的数据,|s| <=1000,q<=1000,n<=1000.
对于另外20%的数据,变量的初值全为0或全为1.
对于100%的数据,1<=lsl<=1x106,1<=q<=1x105,2<=n<=1 х 10^5.
其中,lsl表示字符串s的长度。

题目解析:

20分程序:
表达式只有&运算,或者表达式只有|运算。

  • 如果只有&运算:如果n个数里有>=2个0,那么无论变哪个数值,因为只能变一个值,那么&的最终结果一定是0;如果n个数里有1个0,如果这个0正好就是输入的那个位置,那么结果为1,否则结果是0。
  • 如果只有|运算:如果n个数里有>=2个1,那么无论变哪个数值,因为只能变一个值,那么|的最终结果一定是1;如果n个数里有1个1,如果这个1正好就是输入的那个位置,那么结果为0,否则结果是1。

30分程序:
首先需要对后缀表达式的处理熟悉:遇到数字,入栈;遇到运算符,取出栈顶的两个数字,进行相应运算符的运算,再把运算结果重新入栈。最终栈里剩下的那个数字即为最终结果。
对输入字符串,分离出数字,运算符,按照上述描述利用栈进行模拟即可。

50分程序:
20分程序+30分程序。

100分程序:
利用&运算和|运算的性质进行优化。&运算里,一方如果是0,那么另一方无论如何变化,结果都不会改变。同样的|运算里,一方如果是1,那么另一方无论如何变化,结果都不会改变。

可以建立表达式的二叉树,然后看看哪些xi的变化会影响结果。

例如,下图中根是&运算,

  • 红色结点原始结果是0,绿色是1,那么绿色结点以下的xi无论怎么变化,都不会改变最终结果是0这个结果。因此只递归到红色结点,绿色停止递归。

  • 若红色原始结果是0,绿色也是0,那么它们以下的任何xi变化都不会改变最终 结果,因为一方变化,另一方肯定还是0。因此不继续递归下去。

  • 若红色原始结果是1,绿色是0,那么红色以下的任何xi改变都不会改变最终结果。因此只递归到绿色结点。

  • 若红色和绿色都为1,那么它们以下的xi的改变都有可能影响到最终结果,因此递归到左右结点。
    在这里插入图片描述

如果上述过程中能递归到某个xi,说明这个xi的改变,就会改变最终结果。若某个xi不能被递归到,说明它的改变并不能影响最终结果。

如何建立表达式二叉树树?例如序列x2 x3 x1 & ! |。x2,x3,x1依次入栈,同时新建二叉树结点。接着遇到&,新建结点,并且把栈顶的两个结点当成这个新建结点的左右子树。遇到|同样建立新结点,同时把栈顶当做它的左子树。遇到|,新建结点,并且把栈顶的两个结点当成这个新建结点的左右子树。
在这里插入图片描述

零一点评:

本题第一个20%的样例非常具有迷惑性,很多同学以为是&和|混杂,实际是单纯只有&,或者单纯只有|。如果读题读对了,20分非常容易骗得。
接下来30%的数据,需要懂得栈和后缀表达式的原理,并且熟悉字符串的操作。对于基础算法掌握好的同学比较容易骗到。
100%的思路是全场最难想的,需要对位运算有很强的洞悉能力,或者练习到过类似的高级数据结构的题目。

20分程序

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
string s;
int n, a[1000005], q;
int main()
{
    getline(cin, s);
    int cnt0 = 0;
    cin >> n;
    for (int i = 1; i <= n; i ++) {
        cin >> a[i];
        if (a[i] == 0) cnt0++;
    }
    if (s.find("&") != -1) {
        cin >> q;
        for (int i = 1; i <= q; i ++) {
            int x;
            cin >> x;
            if (cnt0 >= 2) cout << 0 << endl;
            else if (cnt0 == 1 && a[x] == 0)
                cout << 1 << endl;
            else cout << 0 << endl;
        }
    } else if (s.find("|") != -1) {
        cin >> q;
        for (int i = 1; i <= q; i ++) {
            int x;
            cin >> x;
            if (n-cnt0 >= 2) cout << 1 << endl;
            else if (n-cnt0 == 1 && a[x] == 1)
                cout << 0 << endl;
            else cout << 1 << endl;
        }
    }
    return 0;
}

30分程序

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
string ss;
int n, a[1000005], q;
int z[1000005], top;
void rev(int x) {
    if (a[x] == 0) a[x] = 1;
    else a[x] = 0;
}
int main()
{
    getline(cin, ss);
    cin >> n;
    for (int i = 1; i <= n; i ++) {
        cin >> a[i];
    }
    cin >> q;
    for (int i = 1; i <= q; i ++) {
        int x;
        cin >> x;
        rev(x);
        // 计算后缀表达式的值
        top = 0;
        string s = ss;
        s += " ";
        while (s.find(" ") != -1) {
            string b = s.substr(0, s.find(" "));
            s.erase(0, s.find(" ") + 1);
            if (b.size() == 0) continue;
            if (b[0] == 'x') {
                int value = 0;
                for (int j = 1; j < b.size(); j ++) {
                    value = value*10 + b[j] - '0';
                }
                z[++top] = a[value];
            } else if (b[0] == '!') {
                z[top] = !z[top];
            } else if (b[0] == '&') {
                int x1 = z[top--];
                int x2 = z[top--];
                z[++top] = x1 & x2;
            } else if (b[0] == '|') {
                int x1 = z[top--];
                int x2 = z[top--];
                z[++top] = x1 | x2;
            }
        }
        cout << z[1] << endl;
        rev(x);
    }
    return 0;
}

100分程序

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int n, a[1000005], q;
int z[1000005], top;
int cnt;
struct tree{
    int l;
    int r;
    int v;
}t[1000005];
bool f[1000005];
bool change[1000005];
bool calc(int i) { // 计算每个二叉树结点的运算结果
    if (t[i].v >= 0) { // 数值结点
        f[i] = a[t[i].v];
        return f[i];
    }
    bool x1 = calc(t[i].l);
    bool x2 = calc(t[i].r);
    if (t[i].v == -1) f[i] = !x1;
    else if (t[i].v == -2) f[i] = x1 & x2;
    else f[i] = x1 | x2;
    return f[i];
}
void affect(int i) { // 看看i结点的运算会不会受左右孩子影响
    if (t[i].v >= 0) { // 如果能递归到数值结点
        change[t[i].v] = 1;//说明这个数值的变化会改变结果
        return;
    }
    if (t[i].v == -1) affect(t[i].l); // !运算
    else if (t[i].v == -2) { // &运算
        if (f[t[i].l] == 1 && f[t[i].r] == 1) {
            affect(t[i].l);
            affect(t[i].r);
        } else if (f[t[i].l] == 0 && f[t[i].r] == 1) {
            affect(t[i].l);
        } else if (f[t[i].l] == 1 && f[t[i].r] == 0) {
            affect(t[i].r);
        }
    } else {  // |运算
        if (f[t[i].l] == 0 && f[t[i].r] == 0) {
            affect(t[i].l);
            affect(t[i].r);
        } else if (f[t[i].l] == 0 && f[t[i].r] == 1) {
            affect(t[i].r);
        } else if (f[t[i].l] == 1 && f[t[i].r] == 0) {
            affect(t[i].l);
        }
    }
}
int main()
{
    char ch;
    while((ch=getchar())!='\n') {
        int v;
        if(ch=='x') {
            ++cnt; //新建表达式树的结点
            scanf("%d",&v);
            t[cnt].v = v;
            z[++top] = cnt;
        }
        else if(ch==' ')continue;
        else {
            ++cnt; //新建表达式树的结点
            if (ch == '!') {
                t[cnt].l = z[top--];
                t[cnt].v = -1;
            } else if (ch == '&') {
                t[cnt].l  = z[top--];
                t[cnt].r  = z[top--];
                t[cnt].v = -2;
            } else {
                t[cnt].l  = z[top--];
                t[cnt].r  = z[top--];
                t[cnt].v = -3;
            }
            z[++top] = cnt;
        }
    }
    scanf("%d",&n);
    for(int i=1; i<=n;i++)
        scanf("%d",&a[i]);
    calc(cnt);
    affect(cnt);
    cin >> q;
    for (int i = 1; i <= q; i ++) {
        int x;
        cin >> x;
        if (change[x]) cout << !f[cnt] << endl;
        else cout << f[cnt] << endl;
    }
    return 0;
}

方格取数(number)

【题目描述】
设有nxm的方格图,每个方格中都有一个整数。现有一只小熊,想从图的左上角走到右下角,每一步只能向上、向下或向右走一格,并且不能重复经过已经走过的方格,也不能走出边界。小熊会取走所有经过的方格中的整数,求它能取到的整数之和的最大值。
【输入格式】
输入文件名为number.in.
第1行两个正整数n,m.
接下来n行每行m个整数,依次代表每个方格中的整数。
【输出格式】
输入文件名为 number.out.
一个整数,表示小熊能取到的整数之和的最大值。
【样例1输入】
3 4
1 -1 3 2
2 -1 4 -1
-2 2 -3 -1
【样例1输出】
9
【样例2输入】
2 5
-1 -1 -3 -2 -7
-2 -1 -4 -1 -2
【样例2输出】
-10
按上述走法,取到的数之和为(-1)+(-1)+(-3)+(-2)+(-1)+(-2)=-10,可以证明为最大值。因此,请注意,取到的数之和的最大值也可能是负数。
【样例3】
见选手目录下的number/number3.in与number/number3.ans
【数据范围与提示】
对于20%的数据,n,m<=5
对于40%的数据,n,m<=50.
对于70%的数据,n,m<=300.
对于100%的数据,1<=n,m<=1000,方格中整数的绝对值不超过10^4.

题目解析

20分程序:
基础搜索算法。每个位置搜索上,下,右三个方向。时间复杂度3nm。

40分程序:
突破点是上,下,右三个方向,并没有左方向。因此可以考虑采用从左向右递推。
但是上下的方向递推是困难的,因此可以考虑分离隐藏的状态,即可以把到达(i,j)这个任务看成是有n种隐藏的可能:从(1,j-1)到达,从(2,j-1)到达,…,从(n,j-1)到达。例如:从(1,j-1)可以到(1,j) 到(2,j),… ,一路向下到达(i,j),第j列上的路径的值直接求加和。分别枚举每种可能,取最大值即可。状态转移:
f[i][j]=max(f[i][j], f[k][j-1]+第j列的第k行到第i行之和)

时间复杂度:O(N3M)
在这里插入图片描述

70分程序:
在40分程序的基础上,进行前缀和
优化。
求【第j列的第k行到第i行之和】这个
问题是一个典型的区间和问题,可以
用前缀和优化。
时间复杂度:O(N2M)

100分程序:
关键在于在同一列是不可能一会上,一会下的,只能向一个方向行走。70分程序缺少了第j列上下格子的直接递推,满分思路考虑对这里进行优化。对于同一个位置,可以分离出三个不同的状态:fl[i][j] (i,j,来自左边),fu[i][j] (i,j,来自上边),fd[i][j] (i,j,来自下边),结果为三者之最大值。
转移时,先算出fl[i][j]=f[i][j-1]+a[i][j];
再从上向下算出fu[i][j]=max(fl[i-1][j], fu[i-1][j])+ +a[i][j]; (注意递推方向)
再从下向上算出fd[i][j]=max(fl[i+1][j], fd[i+1][j])+ +a[i][j]; (注意递推方向)
结果即f[i][j]=max(fl[i][j], fu[i][j], fd[i][j]);
时间复杂度:O(NM)

在这里插入图片描述

零一点评:

本题第一个20%的样例考察基础搜索算法。搜索算法掌握好的同学很容易骗到20分。40分的程序需要有比较好的动态规划基础,清楚无后效性,递推方向,分离状态等技巧。
40分->70分采用前缀和非常好优化,基础前缀和算法掌握好的同学只要得到40分,不难得70分。
100分的思路属于更高级的动态规划的优化思路,不容易想到,需要有大量动态规划实战经验。

20分程序

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int n, m, a[1005][1005], used[1005][1005], ans = 0x80000000;
int di[4] = {0,0,1,-1};
int dj[4] = {0,1,0,0};
void go(int i, int j, int s) {
    if (i == n && j == m) {
        ans = max(ans, s);
        return;
    }
    for (int k = 1; k <= 3; k ++) {
        int newi = i + di[k];
        int newj = j + dj[k];
        if (newi >= 1 && newi <= n && newj >= 1
                && newj <= m && used[newi][newj] == 0) {
            used[newi][newj] = 1;
            go(newi, newj, s + a[newi][newj]);
            used[newi][newj] = 0;
        }
    }
}
int main()
{
    cin >> n >> m;
    for (int i= 1; i <= n; i ++) {
        for (int j = 1; j <= m; j++) {
            cin >> a[i][j];
        }
    }
    used[1][1] = 1;
    go(1, 1, a[1][1]);
    cout << ans;
    return 0;
}

40分程序

// 没有使用前缀和 预计40分
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int n, m, a[1005][1005];
int f[1005][1005];
int main()
{
    cin >> n >> m;
    for (int i= 1; i <= n; i ++) {
        for (int j = 1; j <= m; j++) {
            cin >> a[i][j];
            f[i][j] = 0x80000000;
        }
    }
    for (int i = 1; i <= n; i ++) {
        int sum = 0;
        for (int l = 1; l <= i; l ++) {
            sum += a[l][1];
        }
        f[i][1] = sum;
    }
    for (int j = 2; j <= m; j ++) {
        for (int i = 1; i <= n; i ++) {
            // 从(k,j-1)开始走第j列的k行->i行,到达(i,j)
            for (int k = 1; k <= i; k++) {
                int sum = 0;
                for (int l = k; l <= i; l ++) {
                    sum += a[l][j];
                }
                f[i][j] = max(f[i][j], f[k][j-1] + sum);
            }
            // 从(k,j-1)开始走第j列的k行->i行,到达(i,j)
            for (int k = i + 1; k <= n; k++) {
                int sum = 0;
                for (int l = i; l <= k; l ++) {
                    sum += a[l][j];
                }
                f[i][j] = max(f[i][j], f[k][j-1] + sum);
            }
        }
    }
    cout << f[n][m];
    return 0;
}

70分程序

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int n, m, a[1005][1005], pre[1005][1005];
int f[1005][1005];
int main()
{
    cin >> n >> m;
    for (int i= 1; i <= n; i ++) {
        for (int j = 1; j <= m; j++) {
            cin >> a[i][j];
            f[i][j] = 0x80000000;
        }
    }
    for (int j = 1; j <= m; j ++) {
        for (int i = 1; i <= n; i ++) {
            pre[j][i] = pre[j][i-1] + a[i][j];
        }
    }
    for (int i = 1; i <= n; i ++) {
        f[i][1] = pre[1][i];
    }
    for (int j = 2; j <= m; j ++) {
        for (int i = 1; i <= n; i ++) {
            // 从(k,j-1)开始走第j列的k行->i行,到达(i,j)
            for (int k = 1; k <= i; k++) {
                // 计算k行->i行的区间和
                int sum = pre[j][i] - pre[j][k-1];
                f[i][j] = max(f[i][j], f[k][j-1] + sum);
            }
            // 从(k,j-1)开始走第j列的k行->i行,到达(i,j)
            for (int k = i + 1; k <= n; k++) {
                // 计算i行->k行的区间和
                int sum = pre[j][k] - pre[j][i-1];
                f[i][j] = max(f[i][j], f[k][j-1] + sum);
            }
        }
    }
    cout << f[n][m];
    return 0;
}

100分程序

#include<iostream>
#include<cstdio>
#include<algorithm>
#define ll long long int
using namespace std;
ll n, m, a[1005][1005];
int f[1005][1005];
int fl[1005][1005], fu[1005][1005], fd[1005][1005];
int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i ++) {
        for (int j = 1; j <= m; j ++) {
            cin >> a[i][j];
        }
    }
    for (int i = 0; i <= n+1; i ++) {
        for (int j = 0; j <= m+1; j ++) {
            f[i][j] = fl[i][j] = fu[i][j] = fd[i][j] = -1e18;
        }
    }
    for (int i = 1; i <= n; i ++) {
        ll sum = 0;
        for (int k = 1; k <= i; k ++) sum += a[k][1];
        f[i][1] = sum;
    }
    for (int j = 2; j <= m; j ++) {
        for (int i = 1; i <= n; i ++) {
            fl[i][j] = f[i][j-1] + a[i][j];
        }
        for (int i = 2; i <= n; i ++) {
            fu[i][j] = max(fl[i-1][j], fu[i-1][j]) + a[i][j];
        }
        for (int i = n-1; i >= 1; i --) {
            fd[i][j] = max(fl[i+1][j], fd[i+1][j]) + a[i][j];
        }
        for (int i = 1; i <= n; i ++) {
            f[i][j] = max(fl[i][j], fu[i][j]);
            f[i][j] = max(f[i][j], fd[i][j]);
        }
    }
    cout << f[n][m];
    return 0;
}

总点评:

第一题,显著难于17、18、19年第一题。往年第一题对于二三等奖区分度不高,希望今年有好的划线。

第二题,果然延续了去年第二题公交换乘对于时间复杂度的考察,但是代码实现起来比18、19年简单一些。

第三题,第一个20%的样例描述十分具有迷惑性,否则非常容易骗分。另外模拟栈实现后缀表达式属于基础算法,无论在初赛备战还是知识点学习时都涉及过。只是因为有字符串操作,需要有熟练的代码实现能力。100分的算法属于本场最难,需要很好的洞察力和数据结构实战能力,对高分段会有一定区分度。

第四题,第一个20%的数据采用经典基础搜索算法即可骗到分,很久没有这么干脆的考察模版搜索问题了,对于虽然不精通但认真学过算法的同学很友好。剩下的思路需要动态规划算法,且要有较好的实战能力,卡出只会模版算法的同学,对于能力高的同学有一定的区分度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值