当前位置: 首页 > news >正文

洛谷 P1650 田忌赛马 题解

题目链接

洛谷 P1650 田忌赛马

F1:动态规划 1

Task 1

根据题目背景的介绍,我们发现总是田忌针对齐王如何出马而决策,且齐王这些马总是要出的,早出晚出无非就是调换顺序,对答案没有影响。所以我们可以假定齐王按从快到慢的速度出马,并在此基础上进行决策。

又根据题目背景的介绍,我们发现田忌会先用最差的马消耗掉齐王最好的马,所以对于当前状态来说,田忌要么直接出最好的马硬刚,要么用最差的马先消耗掉对方,否则就有点浪费了。所以我们可以先对 \(a_i,b_i\) 进行排序,方便后续处理。

然后,定义 \(dp_{i,j}\) 表示前 \(i\) 场比赛中田忌出了最好的 \(j\) 匹马,最差的 \(i-j\) 匹马,能获得的最大金币数;\(f(x,y)\) 表示田忌的第 \(x\) 匹马与齐王的第 \(y\) 匹马比赛,田忌的金币数的增加值(\(200,0,-200\))。

则对于当前状态,田忌可以是出了当时最好的一匹马,即第 \(j\) 头马后得到的,则 \(dp_{i,j}=dp_{i-1,j-1}+f(j,i)\);或者是出了最差的一匹马,即第 \(n-(i-1-j)=n-i+j+1\) 头马后得到的,则 \(dp_{i,j}=dp{i-1,j}+f(n-i+j+1,i)\)。综上,状态转移方程如下,其中 \(i,j,x,y\) 与定义中含义相同。

\[\begin{aligned} dp_{i,j}&=\max{(dp_{i-1,j-1}+f(j,i),dp_{i-1,j}+f(n-i+j+1,i))} \\ \text{其中 }dp_{i,0}&=dp_{i-1,0}+f(n-i+1,i),dp_{i,i}=dp_{i-1,i-1}+f(i,i), \\ f(x,y)&=\begin{cases}200&,a_x>b_y \\0&,a_x=b_y \\-200&,a_x<b_y\end{cases} \end{aligned} \]

代码如下。时间复杂度 \(O(n^2)\),空间复杂度 \(O(n^2)\)

#include<bits/stdc++.h>
using namespace std;const int N=2010;
int n;
int a[N],b[N],dp[N][N];inline int f(int x,int y){if (a[x]>b[y]) return 200;else if (a[x]==b[y]) return 0;else return -200;
}
int main(){scanf("%d",&n);for (int i=1;i<=n;++i) scanf("%d",a+i);for (int i=1;i<=n;++i) scanf("%d",b+i);sort(a+1,a+n+1,greater<int>()); // 排序sort(b+1,b+n+1,greater<int>());for (int i=1;i<=n;++i){dp[i][0]=dp[i-1][0]+f(n-i+1,i),dp[i][i]=dp[i-1][i-1]+f(i,i);for (int j=1;j<i;++j) dp[i][j]=max(dp[i-1][j-1]+f(j,i),dp[i-1][j]+f(n-i+j+1,i));}int ans=INT_MIN; // 注意可能一定赔钱for (int i=1;i<=n;++i) ans=max(ans,dp[n][i]);printf("%d",ans);return 0;
}

Task 2

观察到 Task 1 中的代码更新 \(dp_{i,j}\) 时只用到了 \(dp_{i-1,j}\)\(dp_{i-1,j}\),考虑滚动优化。由于更新时要用到的数据在当前格(\(dp_{i-1,j}\))或前一个(\(dp_{i-1,j-1}\)),我们应倒序枚举。即先更新 \(dp_i\),然后倒序更新 \(dp_{[i-1..1]}\),最后更新 \(dp_0\)

代码如下,时间复杂度不变,空间复杂度 \(O(n)\)

#include<bits/stdc++.h>
using namespace std;const int N=2010;
int n;
int a[N],b[N],dp[N];inline int f(int x,int y){if (a[x]>b[y]) return 200;else if (a[x]==b[y]) return 0;else return -200;
}
int main(){scanf("%d",&n);for (int i=1;i<=n;++i) scanf("%d",a+i);for (int i=1;i<=n;++i) scanf("%d",b+i);sort(a+1,a+n+1,greater<int>());sort(b+1,b+n+1,greater<int>());for (int i=1;i<=n;++i){dp[i]=dp[i-1]+f(i,i);for (int j=i-1;j>=1;--j) dp[j]=max(dp[j-1]+f(j,i),dp[j]+f(n-i+j+1,i));dp[0]=dp[0]+f(n-i+1,i);}int ans=INT_MIN;for (int i=1;i<=n;++i) ans=max(ans,dp[i]);printf("%d",ans);return 0;
}

F2:区间 DP

考虑区间 DP。首先还是一样,观察到田忌只会出最好的马或最差的马。我们定义 \(dp_{i,j}\) 表示当齐王按从强到弱的顺序出,田忌还剩 \(a_{[i..j]}\) 这些马时,田忌所得的金币最多是多少;\(f(x,y)\) 表示田忌的第 \(x\) 匹马与齐王的第 \(y\) 匹马比赛,田忌的金币数的增加值(\(200,0,-200\))。显然此时要对战的是齐王第 \(n+i-j\) 头马。

所以对于初始化,\(dp_{i,i}\) 指田忌第 \(i\) 匹马对战齐王第 \(n\) 匹马,即 \(f(i,n)\)。然后,对于 \(dp_{i,j}\),它可以由 \(dp_{i+1,j}\) 转移而来,即 \(dp_{i,j}=dp_{i+1,j}+f(i,n+i-j)\);也可以由 \(dp_{i,j-1}\) 转移而来,即 \(dp_{i,j}=dp_{i,j-1}+f(j,n+i-j)\)。所以状态转移方程如下,其中 \(i,j,x,y\) 含义如前文所述。

\[\begin{aligned} dp_{i,j}&=\max{(dp_{i+1,j}+f(i,n+i-j),dp_{i,j-1}+f(j,n+i-j))} \\ \text{其中 }dp_{i,i}&=f(i,i),f(x,y)=\begin{cases}200&,a_x>b_y \\0&,a_x=b_y \\-200&,a_x<b_y\end{cases} \end{aligned} \]

代码如下,时空复杂度均为 \(O(n^2)\)

#include<bits/stdc++.h>
using namespace std;const int N=2010;
int n;
int a[N],b[N],dp[N][N];inline int f(int x,int y){if (a[x]>b[y]) return 200;else if (a[x]==b[y]) return 0;else return -200;
}
int main(){scanf("%d",&n);for (int i=1;i<=n;++i) scanf("%d",a+i);for (int i=1;i<=n;++i) scanf("%d",b+i);sort(a+1,a+n+1,greater<int>());sort(b+1,b+n+1,greater<int>());for (int l=1;l<=n;++l){for (int i=1,j=l;j<=n;++i,++j) // 初始化也符合此代码,所以无需单独初始化dp[i][j]=max(dp[i+1][j]+f(i,n+i-j),dp[i][j-1]+f(j,n+i-j));}printf("%d",dp[1][n]);return 0;
}

F3:贪心

首先还是先从大到小排序。假设当前田忌出第 \(i\) 匹马,齐王出第 \(j\) 匹马,则当 \(a_i>b_j\) 时,直接拿下对方。具体情况可见下图枚举。同理,当 \(a_i\le b_j\) 时,我们再比较最弱的马。如果最弱的马能拿下对方最弱的,就先拿分;如果拿不下,就只能让它消耗掉敌方最强的马。具体情况自己画图分析。

P1650 - 1.png

所以总结一下,记田忌此时剩余的马为 \(a_{[l..r]}\),齐王此时剩余的马为 \(b_{[p..q]}\)

  • \(a_l>b_p\) 时,\(l\leftarrow l+1,p\leftarrow p+1,ans\leftarrow ans+200\),即田忌用最好马拿下齐王最好马;
  • \(a_l\le b_p\) 时:
    • \(a_r>b_q\) 时,\(r\leftarrow r-1,q\leftarrow q-1,ans\leftarrow ans+200\),即田忌用最差马拿下齐王最差马;
    • \(a_r\le b_q\) 时,\(r\leftarrow r-1,p\leftarrow p+1,ans\leftarrow ans-\begin{cases}0&,a_r=b_p \\200&,a_r<b_p\end{cases}\),即田忌用最差马消耗掉齐王最好马。

代码如下,时间复杂度 \(O(n\log{n})\),瓶颈在排序。

#include<bits/stdc++.h>
using namespace std;const int N=2010;
int n,ans;
int a[N],b[N];int main(){scanf("%d",&n);for (int i=1;i<=n;++i) scanf("%d",a+i);for (int i=1;i<=n;++i) scanf("%d",b+i);sort(a+1,a+n+1,greater<int>());sort(b+1,b+n+1,greater<int>());int l=1,r=n,p=1,q=n;while (n--){if (a[l]>b[p]) ++l,++p,ans+=200;else if (a[r]>b[q]) --r,--q,ans+=200;else ans-=(a[r]==b[p]?0:200),--r,++p; // 注意更新顺序}printf("%d",ans);return 0;
}
http://www.jsqmd.com/news/295043/

相关文章:

  • 计算机Java毕设实战-基于springboot的自行车分享平台共享单车、租聘信息、归还结算【完整源码+LW+部署说明+演示视频,全bao一条龙等】
  • 2026.1.24 作业 - # P1441 砝码称重
  • Java毕设项目:基于springboot的智能药箱系统(源码+文档,讲解、调试运行,定制等)
  • Java毕设项目:基于springboot的自行车分享平台(源码+文档,讲解、调试运行,定制等)
  • 【课程设计/毕业设计】基于JAVA的自行车分享平台 骑行装备分享系统基于springboot的自行车分享平台【附源码、数据库、万字文档】
  • Java计算机毕设之基于Spring Boot的自行车共享租赁平台开发 Spring Boot驱动的智能共享单车租基于springboot的自行车分享平台(完整前后端代码+说明文档+LW,调试定制等)
  • 【毕业设计】基于springboot的自行车分享平台(源码+文档+远程调试,全bao定制等)
  • 2026.1.24 - # P1441 砝码称重
  • CF946G Almost Increasing Array 题解
  • 2026.1.24 作业 - # P13521 [KOI 2025 #2] 包
  • 国产PCB阻抗测试分析仪:Bamtone班通怎么样?
  • 降AIGC率网站排名榜单:10大热门平台及免费付费功能对比
  • 【毕业设计】基于springboot的智能药箱系统(源码+文档+远程调试,全bao定制等)
  • YOLO26改进 - SPPF模块 | AIFI基于注意力的尺度内特征交互:替代SPPF构建高效混合编码器,提升模型综合效能
  • 2026.1.24 作业 - # P1362 兔子数
  • YOLO26改进 - SPPF模块 | 替代SPPF,FFocal Modulation焦点调制:即插即用轻量设计优化全局语义捕获
  • 大模型微调技术详解:从LoRA到P-Tuning v2,一文掌握高效微调方法
  • 用通俗的方式介绍大语言模型训练过程,非常详细收藏我这一篇就够了
  • 程序员收藏!AI产品经理转型与大模型学习全攻略,抢占AI时代先机,传统PM如何快速转型成AI产品经理?
  • 大模型训练全攻略:从监督学习到数据预处理的完整指南
  • 字节序及IP地址转换
  • LeetCode 134. 加油站(O(n)时间+O(1)空间最优解)
  • 【计算机毕业设计案例】基于Springboot的幼儿园综合管理系统基于springboot的幼儿园管理系统基于SpringBoot+Vue的幼儿园管理系统(程序+文档+讲解+定制)
  • 提示工程架构设计实战:旅游行业智能推荐提示系统架构设计全流程
  • 【计算机毕业设计案例】基于Java的养老院管理系统的设计与实现基于springboot的养老院管理系统的设计与实现(程序+文档+讲解+定制)
  • 深度学习篇---初看transformer
  • 固高控制板卡驱动安装教程
  • 基于大数据的图书推荐系统的设计与实现-计算机毕业设计源码+LW文档
  • 学术研究的第一步不再困难,AI工具助你轻松优化开题报告模板内容
  • 想要高效完成学术写作?这份AI辅助的开题报告模板是你的最佳选择