「模拟赛20190327」 第二题 DP+决策单调性优化
题目描述
小火车虽然很穷,但是他还是得送礼物给妹子,所以他前往了二次元寻找不需要钱的礼物。
小火车准备玩玩二次元的游戏,游戏当然是在一个二维网格中展开的,网格大小是的,某些格子是好的,其余的则是不好的。每次你可以选择最底层(也就是第层)的某两个相邻的列,并消掉最底下的至多三个格子,并且这两列都得有格子被消掉(也就是型或者反着的型),消掉格子以后上面的格子会掉落下来。当然最上面的空位会用不好的格子填满。
小火车想得到所有的好格子送给妹子,请问至少得消多少次呢?
输入
第一行两个整数,表示网格大小。
接下来行每行一个长度为的字符串,表示这个网格,如果为*
则是好格子,为#
就不是。
输出
一行一个整数表示答案。
样例
样例输入
3 2
#*
*#
##
样例输出
2
数据范围
对于的数据
对于的数据满足
对于的数据满足
题解
诈尸啦诈尸啦。
唔……全场码量最小的题。(然而也是唯一没想出来的题)
方法很多,不过有几个很玄幻,所以我采用的是其中一种巧妙、易懂又好写的方法(没有耐心的可直接看解法)。
解法:暴力模拟+搜索,期望得分:。
解法:看出来这个和好格子的分布没关系,只与每一列最高的好格子有关系,然后表示前列中,列都消除完了,第列恰好消除了个最少需要花费多少步,暴力枚举第列的个是放置了个反型,可以轻松计算出型的个数为,暴力,复杂度,期望得分:。常数优秀的选手(真实案例:滚动数组+每次只枚举到这一列的最大高度+枚举反型而不是枚举型获得的常数)可以获得。(弥天大雾
解法:考虑优化解法,找最优决策点太慢了,我们随意脑补一下就知道函数值和肯定是一个单谷函数的关系,因为反型太多会造成自己的浪费,太少又会导致前面的浪费,感性理解可得单谷性。所以三分——等等,别忘了——这是整数,如果最后算出来相等,是无法得知该往哪边靠的。如图,你不知道你现在是红还是蓝还是绿……
所以引入一个玄学操作:当接近到一定程度时,就开始暴算。这个算法一看就非常玄学,你要是仔细拿捏这个度,理论上也是能过的(其实也是能过的),时间复杂度,期望得分:。
解法:考虑其他优化,大佬提供了一个单调队列优化。容易发现,当相邻的两列比例在之间时,最优方案是混合使用型和反型,总花费,然后枚举第列与第列一起被消去的部分长度为多少,对第列被消去的部分进行讨论。如果是比例小于或者大于,那么就是全部都放的是型或者反型,这个很好算,否则是两个混搭起来,也就是第二维下标在当前枚举的第列被消去的高度的一半和两倍之间的上一列的值,这一坨,由于要整除再上取整,为了避免误差,我们按照对取余的值分成三坨,这样每一坨内部的就可以用单调队列搞。当更改第列的时候,就按照常规操作更新单调对列,这样复杂度是,期望得分:。
是不是没听懂?我也没听懂。这个方法随口还是很容易说的,但是写起来可能就没有那么容易了,因此需要更简单的方法。
解法:我们看到解法是不是感觉很不爽,明明知道一个性质却不能用……于是我们继续思考有没有能够一起使用的性质?
首先,可以显而易见地发现,当增大时,的最优决策点是不会变小的,因为第列需要消除更多了,如果反型的变少是对消除不利的,会造成更多的浪费。那么我们考虑暴算,利用解法的单谷性,一旦暴算到一个决策点使得答案更差,那么后面必然不可能更优了,直接退出。再利用决策点单调性,当增加的时候只需要接着上一次的继续暴算就行了,因此第三维的复杂度均摊下来是的,代码非常好写,时间复杂度,期望得分:。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define N 2005
#define inf 0x3f3f3f3f
void Read(int &p)
{
p = 0;
char c = getchar();
for (; c < '0' || c > '9'; c = getchar());
for (; c >= '0' && c <= '9'; c = getchar())p = p * 10 + c - '0';
}
int n, m, h[N];
int f[N][N];
char S[N][N];
int main()
{
Read(n), Read(m);
for (int i = 1; i <= n; i++)
{
scanf("%s", S[i] + 1);
for (int j = 1; j <= m; j++)
if (S[i][j] == '*')
h[j] = max(h[j], n - i + 1);
}
memset(f, 0x3f, sizeof(f));
f[0][0] = 0;
//f[i][j]表示前i-1列已经被消除完,第i列被消除了j个,最少花费的步数
for (int i = 1; i <= m; i++)
{
int lst = 0;
for (int j = 0; j <= h[i]; j++)
{
for (int k = lst; 2 * k <= j; k++)//k表示第i列用了多少个J型,显然随着j的增大,k必定不会减少
{
int w = f[i - 1][max(0, h[i - 1] - k - 2 * (j - 2 * k))] + k + (j - 2 * k);
if (w > f[i][j])break;
f[i][j] = w;
lst = k;
}
}
}
printf("%d\n", f[m][h[m]]);
}
作者:ModestStarlight
出处:http://www.cnblogs.com/ModestStarlight/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现