洛谷P7078 [CSP-S2020] 贪吃蛇 题解
比赛里能做出这题的人真的非常厉害,至少他的智商和蛇一样足够聪明。
首先有一个结论:
当前最强的蛇吃了最弱的蛇之后,如果没有变成最弱的蛇,他一定会选择吃!
证明:
假设当前最强的蛇叫石老板。
如果下一条最强的蛇如果依旧是石老板,那肯定不吃白不吃;
如果下一条最强蛇不是石老板,此时最强的蛇没有原先强,最弱的蛇也没原先弱,吃掉后肯定比石老板要弱。也就是说,当前最强的蛇吃了之后,如果会死,也会死在石老板前面。那么这样一来,这条蛇会想尽办法不死,从而石老板也一定能不死。
有了这个结论,一部分蛇可以放心大胆地吃了,但是问题来了,如果吃了之后变成最弱的蛇了,到底选择吃不吃呢?
稍微往后推一推就明白了:
当前最强蛇记为石老板,下一条最强蛇记为喵老板。石老板进食后变成最弱的蛇了,如果喵老板进食后不是最弱的蛇,他就会选择吃(根据开头的结论),这样石老板就凉了,所以石老板当初的选择一定是不吃。
如果喵老板进食后依旧是最弱的蛇,那就会考虑下一条最强蛇的情况,起名为汪老板。同样分两种情况:如果汪老板进食后不是最弱的蛇,那他就会选择吃,这样喵老板就凉了,所以他当初会选择不吃,这样石老板就不会死,那么石老板当初就会选择吃。如果汪老板进食后变成了最弱的蛇,那就再考虑下一条蛇………………
这个问题就变成了一个递归的问题了,直到某条蛇吃了之后不是最弱的蛇或者只能下两条蛇为止。这样,最后一条蛇会选择吃,倒数第二条蛇为了保命会选择不吃,倒数第三条蛇可以放心大胆的吃,倒数第四条蛇会保命选择不吃,倒数第五条蛇可以放心吃………………
这样,石老板选择吃不吃,就和最后一条蛇之间的奇偶性相关了。并且石老板选择不吃,游戏结束,石老板选择吃,游戏也会在下一轮结束(因为喵老板会选择不吃)。
到目前为止,这个题目很清晰了,只需模拟两个阶段即可:
阶段一:所有最强蛇进食后都不是最弱蛇,放心大胆吃!
阶段二:所有最强蛇进食后都是最弱蛇,直到有条蛇可以放心吃为止(吃了后不是最弱或者只剩两条)
阶段一结束时,游戏就基本结束了(根据阶段二的奇偶性看能不能再吃一次)
利用set可以方便地维护最强、最弱蛇,时间复杂度\(O(Tnlogn)\),只能拿 70 分,代码如下:
int a[N];
int main() {
int _;
scanf("%d", &_);
int n;
for (int cas = 1; cas <= _; cas++) {
if (cas == 1) {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
} else {
int k;
scanf("%d", &k);
while (k--) {
int x, y;
scanf("%d%d", &x, &y);
a[x] = y;
}
}
set<pair<int, int> > se;
for (int i = 1; i <= n; i++) {
se.insert({a[i], i});
}
int flag = 0, ans;
while (1) {
if (se.size() == 2) {
se.erase(se.begin());
if (flag) {
if ((flag - se.size()) % 2) {
ans = flag + 1;
} else {
ans = flag;
}
} else
ans = 1;
break;
}
set<pair<int, int> >::iterator it = se.end();
it--;
int x = it->first, id = it->second;
int y = se.begin()->first;
se.erase(it);
se.erase(se.begin());
se.insert({x - y, id});
if (se.begin()->second != id) {
if (flag) {
if ((flag - se.size()) % 2) {
ans = flag + 1;
} else {
ans = flag;
}
break;
}
} else {
if (flag == 0) flag = se.size();
}
}
printf("%d\n", ans);
}
return 0;
}
正解:用两个双端队列\(q_1,q_2\)维护即可。
先把初始的有序蛇放进\(q_1\)里,此时\(q_1\)已满足单调性,头部小,尾部大,我们后面会让\(q_2\)也满足这样的单调性。
第一阶段:
每次从\(q_1,q_2\)的尾部取出最强的蛇,从\(q_1\)头部取出最弱的蛇,如果吃了以后是最弱的,那就进入第二阶段,否则直接装入\(q_2\)的头部。
为什么可以装进\(q_2\)并且是\(q_2\)里最弱的?其实证明最初的结论时已经解释过了,后面进食的蛇肯定是越来越弱的,而且这个阶段最弱的蛇一定在\(q_1\)中。
第二阶段:
此时最弱的蛇,就没必要丢进队列里了,单独维护一下就好了,因为连续的一段进食后都是最弱的,直到总蛇数等于 22 或者进食后不是最弱为止,而最强的依旧从 \(q_1,q_2\)的尾部找。
这样就不需要用其他带\(log\)的数据结构维护了,时间复杂度\(O(Tn)\),代码如下:
int a[N];
int main() {
int _;
scanf("%d", &_);
int n;
for (int cas = 1; cas <= _; cas++) {
if (cas == 1) {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
} else {
int k;
scanf("%d", &k);
while (k--) {
int x, y;
scanf("%d%d", &x, &y);
a[x] = y;
}
}
deque<pair<int, int> > q1, q2;
for (int i = 1; i <= n; i++) {
q1.push_back({a[i], i});
}
int ans;
while (1) {
if (q1.size() + q2.size() == 2) {
ans = 1;
break;
}
int x, id, y;
y = q1.front().first, q1.pop_front();
if (q2.empty() || !q1.empty() && q1.back() > q2.back()) {
x = q1.back().first, id = q1.back().second, q1.pop_back();
} else {
x = q2.back().first, id = q2.back().second, q2.pop_back();
}
pair<int, int> now = make_pair(x - y, id);
if (q1.empty() || q1.front() > now) {
ans = q1.size() + q2.size() + 2; // 不吃
int cnt = 0;
while (1) {
cnt++;
if (q1.size() + q2.size() + 1 == 2) {
if (cnt % 2 == 0) ans--;
break;
}
int x, id;
if (q2.empty() || !q1.empty() && q1.back() > q2.back()) {
x = q1.back().first, id = q1.back().second, q1.pop_back();
} else {
x = q2.back().first, id = q2.back().second, q2.pop_back();
}
now = {x - now.first, id};
if ((q1.empty() || now < q1.front()) && (q2.empty() || now < q2.front())) {
;
} else {
if (cnt % 2 == 0) ans--;
break;
}
}
break;
} else {
q2.push_front(now);
}
}
printf("%d\n", ans);
}
return 0;
}
转自OMG_wc 的博客
原文地址:https://www.cnblogs.com/zhangshaojia/p/15006045.html
- Django 1.10中文文档-第一个应用Part7-自定义管理站点
- 转--Golang语言--复合数据
- 三步学会用spring开发OSGI——(第三步:web篇)
- Python标准库笔记(5) — sched模块
- 设计模式(4)-序列生成器之单例模式
- PyCharm 自定义文件和代码模板
- kotlin到底好在哪里?
- Django 1.10中文文档-第一个应用Part6-静态文件
- Django 1.10中文文档-第一个应用Part5-测试
- 设计模式(5)-己所不欲,施之于人(代理模式)
- Python标准库笔记(4) — collections模块
- 使用captcha模块生成图形验证码
- 设计模式(6)-装饰器(认识程序中的装饰器)
- Selenium Webdriver常用方法
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Android自定义View实现游戏摇杆键盘的方法示例
- 详解Android Libgdx中ScrollPane和Actor事件冲突问题的解决办法
- Android ImageView的selector效果实例详解
- 完美解决关于禁止ViewPager预加载的相关问题
- Android开发之OpenGL绘制2D图形的方法分析
- Android实现RecyclerView下拉刷新效果
- 详解 Android中Libgdx使用ShapeRenderer自定义Actor解决无法接收到Touch事件的问题
- Android Shader应用开发之雷达扫描效果
- Android开发之绘制平面上的多边形功能分析
- Android Surfaceview的绘制与应用
- Android SQLite数据库版本升级的管理实现
- Android自定义view实现拖拽选择按钮
- Android 中ViewPager中使用WebView的注意事项
- Android IPC机制Messenger实例详解
- Android开发之文本内容自动朗读功能实现方法