蓝桥杯补题

目录

组合型枚举 + DPS 

思路:组合型枚举所有可能,dfs搜索判断

状态压缩DP

例题1

例题2

并查集

递归 

前缀和 + 公式变形

数论 + 完全背包

数论的一些结论:




组合型枚举 + DPS 

P1286 - [蓝桥杯2016初赛]剪邮票 - New Online Judge (ecustacm.cn)

一个误区:首先,本题不能使用深搜,因为深搜是一条路走到黑的,但是本题可以出现土字形的形状,这是深搜搜不出来的! 

思路:组合型枚举所有可能,dfs搜索判断

题目要求我们找出五个相邻的方块的不同组合,那么我们可以枚举这五个方块的位置,并这只一个长为12的数组,1表示该位置选定,枚举的时候可以使用全排列函数。

虽然我们枚举的数组长度是12,但是因为数组中的元素只有0或者1,所以集合非常少,一共又792种组合,对于每一种组合,我们只需要dfs搜索一下所有的连通块,只有连通块个数为1的时候,这个组合构成的图形才是联通的。

注意在使用next_permutation函数的时候,我们初始化的数组必须是最小的字典序!

因为该函数是按照字典序枚举的,你如果本来字典序就是最大的,那么它还枚举什么?

如果a[12]={1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 },那么程序就运行一次

#include <iostream>
#include <cstring>
#include <bits/stdc++.h>
#include <algorithm>

using namespace std;

int a[12] = {0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1};
int b[10][10];
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

void dfs(int i, int j)
{
    b[i][j] = 0;
    for(int k = 0; k < 4; k ++ )
    {
        int x = i + dx[k], y = j + dy[k];
        if(!b[x][y] || x < 0 || y < 0 || x > 3 || y > 4)   continue;
        dfs(x, y);
    }
}

bool check()
{
    for(int i = 0; i < 12; i ++ )
        b[i / 4][i % 4] = a[i];
        
    int cnt = 0;
    for(int i = 0; i < 3; i ++ )
        for(int j = 0; j < 4; j ++ )
            if(b[i][j])
            {
                dfs(i, j);
                cnt ++ ;
            }
            
    return cnt == 1;
}

int main()
{
    int res = 0;
    do{
        if(check()) res ++ ;
    }while(next_permutation(a, a + 12));
    
    cout << res << endl;
    
    return 0;
}



状态压缩DP

例题1

P1554 - [蓝桥杯2021初赛] 回路计数 - New Online Judge (ecustacm.cn)

 互质数的原则

只要两数的公因数只有1时,就说两数是互质数

注意:

在状态压缩DP中,时刻小心位移、与、或运算与加号、减号的优先级问题,前者的优先级小,要用括号括起来!

参考:

(11条消息) 2021年蓝桥杯A组省赛-回路计数_不牌不改的博客-CSDN博客

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 22, M = 1 << N;

long long f[M][N];
bool g[N][N];

int gcd(int a, int b)  // 欧几里得算法
{
    return b ? gcd(b, a % b) : a;
}

int main()
{
    int n = 21;
    for(int i = 1; i <= n; i ++ )
        for(int j = i + 1; j <= n; j ++ )
            if(gcd(i, j) == 1)
            {
                g[i - 1][j - 1] = 1;//从1~n映射成0~n-1
                g[j - 1][i - 1] = 1;//可以提升效率,方便计算
            }
    
    f[1][0] = 1; //初始化,f[s][0]=1,表示自己到自己是一种方案。
    //可以理解为,此时,1既是起点,又是它自己的终点和1~20中一个子区间[1,1]的终点!
    //时刻理解我们的终点并不是最终的终点,而是任意子区间的终点!
    for(int i = 0; i < 1 << n; i ++ )//枚举每一种状态
        for(int j = 0; j < n; j ++ )//枚举每个终点
            if(i >> j & 1)//如果走过这个点
                for(int k = 0; k < n; k ++ )//转折点
                    if((i >> k & 1) && g[j][k])//走过这个点并且可以转移过去
                        f[i][j] += f[i - (1 << j)][k];
	   
    long long res = 0;
    for(int i = 1; i < n; i ++ )
        res += f[(1 << n) - 1][i];
    cout << res << endl;
    return 0;
}


例题2

补充(模板题)求最短哈密顿回路:91. 最短Hamilton路径 - AcWing题库

关于本题为什么考虑压缩状态:

我们考虑这么一种情况,有20个点(1,2,...,20),我们求它的最短哈密顿路径,假设我们当前之走了三个点,对于以下两种情况:

  • 1-> 3-> 5:18
  • 1-> 5-> 3:20

我们当前已经走过了1,3,5三个点,并且按照1->3->5走的距离更短,那么,从1->5->3继续走下去的所有情况我们都不用再考虑了!因为对于1->5->往后的任何状态,我们都可以从路径更短的1->3->5转移过去,1->5->3就是一个最优子结构!

我们只需要考虑最后停在那个点,以及中间那个点被用过(即这条路径的状态)。对于所有的终点,我们可以通过一个中间点转移过去。这就是状态压缩。

注意:上面的终点并不是说最后停在那个点,例如对于1->2->3->4,那么对于这段路径的一段子路径2->3,我们可以说3是个终点,只不过是一个子区间的终点!同理,2,3,4皆可以作为一个区间的终点!

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

const int N=20,M=1<<N;

int f[M][N],w[N][N];//w表示的是无权图

int main()
{
    int n;
    cin>>n;

    for(int i=0;i<n;i++)
     for(int j=0;j<n;j++)
      cin>>w[i][j];

    memset(f,0x3f,sizeof(f));//因为要求最小值,所以初始化为无穷大
    f[1][0]=0;//因为零是起点,所以0已经走过了,所以二进制第0位的状态位1,2^0=1,所以f[1][0]=0;

    for(int i=0;i<1<<n;i++)//i表示所有的情况
     for(int j=0;j<n;j++)//j表示走到哪一个点
      if(i>>j&1)
       for(int k=0;k<n;k++)//k表示走到j这个点之前,以k为终点的最短距离
        if(i>>k&1)
         f[i][j]=min(f[i][j],f[i-(1<<j)][k]+w[k][j]);//更新最短距离

    cout<<f[(1<<n)-1][n-1]<<endl;//表示所有点都走过了,且终点是n-1的最短距离
    //位运算的优先级低于'+'-'所以有必要的情况下要打括号
    return 0;
}

关于下面讨论的一些理解:

  • 首先,为什么要在外层循环路径,内层循环终点?我们用反证法,让外层循环终点,假设我们循环到了一个状态111,表示第1,2,3个点都走过了,这时候我们需要状态转移,但如果我们此时外层循环的终点只是1的话,那么这个点的状态还没有被更新,所以不是最优的,往后的所有状态都不是最优的
  • 其次,DP状态要按拓扑序计算,即,状态转移需要的状态需要我们提前计算出来。因为我们要保证局部最优子结构!我们不能用一个不是最优的答案去更新另一个答案,这会导致另一个答案肯定不是最优的答案。



并查集

[蓝桥杯2019初赛]修改数组

先看一下暴力做法,看看有什么优化的地方 

#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_set>
 
#define int long long
 
using namespace std;
 
const int N = 100010;
 
int n, w[N];
unordered_set<int> s;
 
signed main()
{
    cin >> n;
    for(int i = 1; i <= n; i ++ )   cin >> w[i];
     
    for(int i = 1; i <= n; i ++ )
    {
        if(!s.count(w[i]))  
        {
            cout << w[i] << " ";
            s.insert(w[i]);
        }
        else
        {
            while(s.count(w[i]))    w[i] ++ ;
            s.insert(w[i]);
            cout << w[i] << " ";
        }
    }
    cout << endl;
     
     
    return 0;
}

通过观察暴力方法,我们可以想到这么一个例子:

1,2,3,......,100000,1,1,1,1,.....1

对于100000往后的所有1,我们都要走100000次才能找到正确答案,而走的这100000次基本上都是重复计算!那么,我们能不能找到一种方法,避免重复计算呢?

我们考虑给每个值input映射一个output,用来表示如果当前输入的值是input,那么我们就输出output,由于output被我们输出了,后面也就不能再用了,所以我们要让output的值加1,这样,当我们下一次选择val时,它会输出一个前面没有用过的output

想到这里,你想到了什么?当然是并查集啊!这个output就是input的根

以下思路:

对于所有的数,起初他们的根节点都指向自己,表示没出现过。当遇到一个数时,找到他的根节点,由于根节点始终是未出现过的,所以输出根节点,接着把根节点再指向一个新的根节点(比旧的根节点大 1),这样就能始终保持根节点是未出现过的了。

在查找根节点的同时使用路径压缩,可以更快的查找到根节点。

6
1 2 3 4 1 3
遇到 1,1 的根节点是 1,输出 1,指向新的根节点 2,也就是 par[1] = 1 + 1;
遇到 2,2 的根节点是 2,输出 2,指向新的根节点 3
遇到 3,3 的根节点是 3,输出 3,指向新的根节点 4
遇到 4,4 的根节点是 4,输出 4,指向新的根节点 5
遇到 1,1 的根节点是 5,输出 5,指向新的根节点 6
遇到 3,3 的根节点是 6,输出 6,指向新的根节点 7
#include <iostream>
using namespace std;
const int MaxNum = 1000005;
int par[MaxNum];
void init() {
    for (int i = 0; i < MaxNum; ++i) par[i] = i;
}

int find(int x) {
    if (par[x] == x)
        return x;
    else
        return par[x] = find(par[x]);
}
int main() {
    int n;
    cin >> n;

    init();
    while (n--) {
        int num;
        scanf("%d", &num);
        int root = find(num);
        printf("%d ", root);
        par[root] = root + 1;
    }
    return 0;
}


思路二:本质上就是没有路径压缩的并查集

当我们仔细研究时发现,我们其实可以去掉多余的访问,例如一个数已经被访问了k次,那么就说明他后面的k个数一定都被访问了,所以我们可以将访问数组visited改为记录访问次数的数组。例如,当visited【x】=k时,正好输入的数是x,则我们直接将x跳到x+k的位置再进行判断是否被访问。

#include <iostream>
using namespace std;
const int MaxNum = 1000005;
int vis[MaxNum];

int main() {
    int n;
    cin >> n;
    while (n--) {
        int num;
        scanf("%d", &num);
        while (vis[num]) {
            int tmp = num;
            num += vis[num];
            ++vis[tmp];
        }
        vis[num] = 1;
        printf("%d ", num);
    }
    return 0;
}


参考(两种思路):

蓝桥杯2019年第十届真题-修改数组-题解(C++代码)-Dotcpp编程社区

蓝桥杯2019年第十届真题-修改数组-题解(C++代码)(链表式并查集 + 这是什么神仙算法)-Dotcpp编程社区



递归 

P1321 - [蓝桥杯2017初赛]正则问题 - New Online Judge (ecustacm.cn)

我的栈模拟做法,在做这道题的时候,我陷入了一个误区,那就是关于‘ | ’号的处理,我们知道在这道题里面 a|b 的意思是取a和b的最大值,但是当我们遍历到‘ | ’的时候,我们只知道a的值,由于b我们还没有遍历,它的值也就无从得知,所以我在考虑的时候,我是想把这个    ‘ | ’符号先标记一下,然后等我们遍历完b之后,再比较大小,但这样的话是不对的。

其实我们不需要先得到b的值再比较,我们只要遇到‘ | ’就比较一次,再结束的时候再比较一次就可以了,其中第一次比较是没有意义的,不会影响结果。

另外本题用递归实现比模拟简单一万倍!主要是要理解怎么递归。

#include<bits/stdc++.h>
using namespace std;

string s;
int pos = 0;       //当前的位置

int dfs(){
	int tmp = 0, ans = 0;
	int len = s.size();
	while(pos < len){
		if(s[pos] == '('){   //左括号,继续递归。相当于进栈
			pos++;
			tmp += dfs();
		}else if(s[pos] == ')'){   //右括号,递归返回。相等于出栈
			pos++;
			break;
		}else if(s[pos] == '|'){   //检查或
			pos++;
			ans = max(ans, tmp);
			tmp = 0;
		}else{                     //检查X,并统计X的个数
			pos++;
			tmp++;
		}
	}
	ans = max(ans, tmp);
	return ans;
}

int main(){
	cin >> s;
	cout << dfs() << endl;
	return 0;
}

 附上我的模拟代码,得了40%的分数

//模拟,得40分
#include <iostream>
#include <cstring>
#include <algorithm>
#include <stack>

using namespace std;

int main()
{
    string s;
    cin >> s;
    stack<int> res;
    int x = 0, n = s.size();
    
    int t = 0;
    bool flag = false, is_new = true;
    
    for(int i = 0; i < n; i ++ )
    {
        if(s[i] == 'x')
        {
            if(!res.size() || is_new) res.push(1);
            else
            {
                int maxn = res.top() + 1;
                res.pop();
                res.push(maxn);
            }
            is_new = false;
        }
        else if(s[i] == '(')    
        {
            is_new = true;
        }
        else if(s[i] == ')')
        {
            is_new = true;
            if(flag)// '|'
            {
                int x = res.top();    res.pop();
                int y = res.top();    res.pop();
                res.push(max(x, y));
                flag = false;
            }
            else
            {
                res.push(t);
                t = 0;
            }
        }
        else if(s[i] == '|')
        {
            is_new = true;
            if(flag)
            {
                int x = res.top();    res.pop();
                int y = res.top();    res.pop();
                res.push(max(x, y));
            }
            else flag = true;
        }
    }
    
    if(flag)
    {
        int x = res.top();    res.pop();
        int y = res.top();    res.pop();
        res.push(max(x, y));
    }
    
    if(res.size())
    {
        int sum = 0;
        while(res.size())
        {
            sum += res.top();
            // cout << "res.top: " << res.top() << endl;
            res.pop();
        }
        cout << sum << endl;
    }
    else cout << 0 << endl;
    
    return 0;
}

参考:[蓝桥杯2017初赛]正则问题 递归

倪文迪陪你学



前缀和 + 公式变形

P1329 - [蓝桥杯2017初赛]k倍区间

 题目让我们求一个区间[L, R]满足(a[L] + a[L + 1] + .... + a[R]) % k = 0

这种区间问题首先应该立马想到前缀和,那么问题就转化成了求(sum[R]-sum[L-1]) % k == 0

对面上面一个公式,由于我们有两个未知量L,R,所以我们需要两重循环来枚举L,R的位置,但这样肯定会超时

我们可以对公式转变一下,再前缀和当中,这种公式的转化非常常用,应该很敏感才对!

(sum[R] - sum[L - 1]) % k == 0        <==>        sum[R] % k == sum[L - 1] % k       

(证明略)

此时,我们把求一个区间的和转化成了求求有多少个区间和模k相等!

这是一个非常大的变化!虽然我们仍然有两个变量位置(L,R)但是我们可以用一个数组cnt来保存sum[i]所有值的和,然后再后续如果当前sum[p]%k==1,那么我们只需要调用一下cnt[1]的值就行了,然后再让cnt[1]++,让后面的数调用。

这样,我们就能在O(N)的时间复杂度内完成计算!

另外,对于这种大数据求和运算,时刻记得爆int的问题!

#include <iostream>
#include <cstring>
#include <algorithm>

#define int long long

using namespace std;

const int N = 100010;

int n, k;
int cnt[N], s[N];

signed main()
{
    cin >> n >> k;
    for(int i = 1; i <= n; i ++ )
    {
        cin >> s[i];
        s[i] += s[i - 1];//注意这里不能预处理求余!
    }
    
    cnt[0] = 1;
    //什么都不选就是0,0%k=0,实际的含义就是我们L-1最小可以为0
    //这种前缀和中对于位置0的预处理也很常见!
    
    int res = 0;
    for(int i = 1; i <= n; i ++ )
    {
        s[i] %= k;
        res += cnt[s[i]] ++ ;
        /*  <=> 
            res += cnt[s[i]];
            cnt[s[i]] ++ ;
        */
    }
    
    cout << res << endl;
    
    return 0;
}



数论 + 完全背包

P1322 - [蓝桥杯2017初赛]包子凑数 - New Online Judge (ecustacm.cn)

 首先献上我的暴力代码,我的思路是把给定数能组合成的数都存下来(暴力DFS),然后挨个遍历,这个有一个特性,就是说如果给定的数不能构成的数是有限个的,那么,肯定存在一个连续的长度大于等于给定数列中最小值的一个序列。

例如,给定x,y,z(升序),那么只要他们组成的数有这么一个序列a1,a2,a3,a3,,,an满足n>=x,那么x,y,z不能组成的数肯定是有限个,因为从a1开始,我们给每个数+x,一直到an,就构成了一个新数列b1,b2,b3,,,bn,由于b1-a1<=n(由n>=x证得)所以a和b之间也是连续的!同理,我们还可以构造c,d,e,,,直到无穷!

这样,我们就实现了判断不能构成的数的个数(遍历),以及构不成的数是不是无穷(是否存在这么一个长度大于最小值的序列)

但是,暴力终究是暴力,由于我们给定的原数列中数的个数可能太多,所以会爆莫名其妙的错误,但这个方法依然拿到了65%的分数!甚至在ACWING只有一个测试点没过!

其次,我当时也不知道最大不能构成的数到底是多大,所以如果我们的size小了,那么结果可能出错,size大了,TLE!

#include <iostream>
#include <cstring>
#include <algorithm>
#include <set>

#define int long long

using namespace std;

const int N = 1010, INF = 0x3f3f3f3f;

bool st[N];
int n, a[N], minx = INF;
int idx;
set<int> s, news;

void dfs()
{
    while(s.size() < N)
    {
        news = s;
        for(auto &x : s)
        {
            for(int i = 0; i < n; i ++ )
                news.insert(a[i] + x);
        }
        s = news;
    }
}

signed main()
{
    cin >> n;
    for(int i = 0; i < n; i ++ )    
    {
        cin >> a[i];
        minx = min(minx, a[i]);
        s.insert(a[i]);
    }
    
    dfs();
    
    bool has_res = false;
    int maxn = -1, t = 0, last = -1, en;
    for(auto &x : s)
    {
        st[x] = true;
        
        if(last == -1)
        {
            last = x;
            t = 1;
        }
        else
        {
            if(x - last == 1)
            {
                last = x;
                t ++ ;
                if(t > minx)
                {
                    has_res = true;
                    en = x;
                    break;
                }
            }
            else
            {
                maxn = max(maxn, t);
                if(maxn > minx)
                {
                    has_res = true;
                    en = x;
                    break;
                }
                last = x;
                t = 1;
            }
        }
    }
    // for(auto &x : s)
    //     cout << x << " ";
        
    if(has_res) 
    {
        int cnt = 0;
        for(int i = 1; i <= en; i ++ )
        {
            if(!st[i])  cnt ++ ;
        }
        cout << cnt << endl;
    }
    else    puts("INF");
        
    // int cnt = 0;
    // if(int i = 1; i < N; i ++ )    
    //     if(!st[i])  
    //         cnt ++ ;
            
    // cout << cnt << endl;
    return 0;
}

正解:数论+完全背包DP

数论的一些结论:

  1. 任意两个数的组合必定是其公约数的倍数(该性质可以拓展到多个数)
  2. 对于任意两个互质(公约数为1)的数a、b,最大的、无法凑成的数为ab−a−b
  3. 第1点解决“凑不出的数目无限个”的问题。当几个数的公约数不为1时,因为它们组合成的数必定为公约数的倍数,所以最起码有无限个质数不能被组成。
  4. 第2点解决“上界”的问题,原题AiAi最大值为100,一百及以内最大的两个互质的数为100和99,其最大不能凑成的数为100∗99−100−99=9701100∗99−100−99=9701,故取上界到9701以上即可

第三点的证明:

  • 反证法,假设公约数不为1时能组成无限个质数。要组成质数,则根据点1该质数必定是公约数的倍数,而质数的因数仅有1和本身,公约数不为1故只能等于该质数本身。因此若质数A可被组成,则公约数为A;若另一个质数B同样能被组成,则公约数应为B,与前者矛盾。所以当公约数不为1时,能组成的质数仅有1个,假设不成立。

上述第二点的证明和例题:

[NOIP2017 提高组] 小凯的疑惑 / [蓝桥杯 2013 省] 买不到的数目 - 洛谷

 DP:

我们可以把题目给定的数字看作一个物品,这个物品的体积就是该数字。把数组能组成的组合看作背包的体积。由于物品可以选无限个,那么就转化成了一个完全背包。只不过背包的属性不是最大价值,而是一个bool值,表示能不能组成。

//二维背包
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 110, M = 10010;

int f[N][M];
int n, d, a[N];

int gcd(int a, int b)
{
    return b ? gcd(b, a % b) : a;
}

int main()
{
    ios::sync_with_stdio(false);
    
    cin >> n;
    for(int i = 1; i <= n; i ++ )   
    {
        cin >> a[i];
        d = gcd(d, a[i]);
    }
    
    // cout << d << endl;
    if(d != 1)  cout << "INF" << endl;
    else
    {
        //完全背包模板
        f[0][0] = 1;//0个数一个数都不选是一种合法的方案
        for(int i = 1; i <= n; i ++ )
            for(int j = 0; j < M; j ++ )
            {
                f[i][j] |= f[i - 1][j];//不选
                if(j >= a[i])   f[i][j] |= f[i][j - a[i]];//选(推导得到)
            }
        
        int cnt = 0;
        for(int i = 0; i < M; i ++ )
            if(!f[n][i])
                cnt ++ ;
                
        cout << cnt << endl;
    }
    
    
    return 0;
}

//一维优化
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 110, M = 10010;

int f[M];
int n, d, a[N];

int gcd(int a, int b)
{
    return b ? gcd(b, a % b) : a;
}

int main()
{
    ios::sync_with_stdio(false);
    
    cin >> n;
    for(int i = 1; i <= n; i ++ )   
    {
        cin >> a[i];
        d = gcd(d, a[i]);
    }
    
    // cout << d << endl;
    if(d != 1)  cout << "INF" << endl;
    else
    {
        //完全背包模板
        f[0] = 1;//0个数一个数都不选是一种合法的方案
        for(int i = 1; i <= n; i ++ )
            for(int j = a[i]; j < M; j ++ )
                f[j] |= f[j - a[i]];
        
        int cnt = 0;
        for(int i = 0; i < M; i ++ )
            if(!f[i])
                cnt ++ ;
                
        cout << cnt << endl;
    }
    
    
    return 0;
}



线性DP + 推结论

P1526 - [蓝桥杯2020初赛] 数字三角形 - New Online Judge (ecustacm.cn)

乍一看是线性DP中《数字三角形》的模板题,其实就是模板题,只不过题目中加了一个限制条件(向左下走的次数与向右下走的次数相差不能超过1)这句话是什么意思呢?我们模拟一下就知道了。

从根节点开始,我们有两种选择(向左走或者向右走),如果我们做出了某种选择,那么之后我们都必须与做出遇上一次选择相反的选择。即,如果我们在根节点向左走,那么往后我们只能走右->左->.....。也就是说,我们的路线就只有两条,一种是从根节点出发向左走的那条,另外一条就是向右走的那条。

并且,当n为奇数时,无论向左走还是向右走,最后都走到叶子的中点。

当n为偶数时,由于没有真正意义上的中点,所以我们有两种选择:把中点看做是浮点数的中点,上取整和下取整。 

j == 1时,即这一行的第一个,dp[i][j]=dp[i-1][j]+M[i][j]//只能从上一层的右边走过来;
j == i时,即这一行的最后一个元素,dp[i][j]=dp[i-1][j-1]+M[i][j]//只能从上一层的左边走过来;
1<j<i时,即一般情况,dp[i][j]=max(dp[i-1][j],dp[i-1][j-1])+M[i][j];//取左边走或右边走的max;

这样在dp的时候考虑边界情况就不用再开始的时候在初始化上功夫了,由于本体的节点值可能是负值(容易忽略的一个点),所以初始化不太简单。

#include<bits/stdc++.h>
using namespace std;
int M[102][102];
int dp[102][102];
int n;
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=i;j++){
            scanf("%d",&M[i][j]);
        }
    }

    for(int i=1;i<=n;i++){
        for(int j=1;j<=i;j++){
            if(j==1){//这一行最左边1元素
                dp[i][j]=dp[i-1][j]+M[i][j];
            }else if(j==i){//这一行最右边元素
                dp[i][j]=dp[i-1][j-1]+M[i][j];
            }else{//一般情况
                dp[i][j]=max(dp[i-1][j],dp[i-1][j-1])+M[i][j];
            }
        }
    }

    if(n%2){//n为奇数只能走到最后一行中间处
        printf("%d",dp[n][n/2+1]);
    }else{//n为偶数有两个终点可供选择,选最大的
        int ans=max(dp[n][n/2],dp[n][n/2+1]);
        printf("%d",ans);
    }
    return 0;
}



posted @ 2022-05-05 08:41  光風霽月  阅读(51)  评论(0编辑  收藏  举报