The Hand
— 摘自 Frank R Wilson 《The Hand》, 来自 Dynamicland 的推荐书单 “没有人在开始时知道他们参与的是什么;不知道需要多长时间,不知道会引向何方。&r
via 夜行人
— 摘自 Frank R Wilson 《The Hand》, 来自 Dynamicland 的推荐书单 “没有人在开始时知道他们参与的是什么;不知道需要多长时间,不知道会引向何方。&r
via 夜行人
The Educated Mind
— 摘自 Kieran Egan 《The Educated Mind》, 来自 Dynamicland 的推荐书单 在⼗六世纪,普通市民发现所有商品的价格开始迅速上涨。最明显的是他们不得不为⾐物等必需品⽀付更
via 夜行人
— 摘自 Kieran Egan 《The Educated Mind》, 来自 Dynamicland 的推荐书单 在⼗六世纪,普通市民发现所有商品的价格开始迅速上涨。最明显的是他们不得不为⾐物等必需品⽀付更
via 夜行人
Simulation and Its Discontents
— 摘自 Sherry Turkle 《Simulation and Its Discontents》, 来自 Dynamicland 的推荐书单 没有什么⽐⼀个新鲜、未开发的机会更能吸引好奇者。 对于希望了解围
via 夜行人
— 摘自 Sherry Turkle 《Simulation and Its Discontents》, 来自 Dynamicland 的推荐书单 没有什么⽐⼀个新鲜、未开发的机会更能吸引好奇者。 对于希望了解围
via 夜行人
【2019牛客暑期多校第一场】E题ABBA
题目链接
大致题意
有$(n + m)$个字母A和$(n + m)$个字母B,组成一个长度为 $2*(n + m)$的字符串,并且使得字符串中有$n$个“AB”和$m$个“BA”,求出可能的组合数(mod 1e9+7)
例如,n = 1 m = 2时,可以有这样的字符串(并不是全部的字符串):
ABBABA
ABBBAA
BBABAA
上面三个字符串均满足条件
解题思路
考虑递推,假设已经有一个字符串满足一定的“先决条件”(此处应当理解为数学归纳法,及假设n - 1时满足)
下面考虑==在字符串最后加入一个字符==的情况。仅有两种可能:加A或者加B(这不是白说吗)
但是考虑一下极端情况,我们可以得到一些简单的且明显的条件(N~A~表示已经在字符串中的A个数,N~B~同理)
假如字符串的组成类似这样:
此时,我们假设 $n$ 为 5
而这个字符只能且必定要组合成 5 个AB,也就是说,我们接下来加入字符,只能加入 A 而不能加入 B
此时我们往前推,如果出现了这样一个字符串,则在之前,必定出现如下状态:
$dp[i] = dp[i - 1] + dp [i + 1]$
其中,dp[i - 1]指的是加入一个B(增加一个B使得N~A~ - N~B~变小)。而dp[i + 1]指的是加入一个A
当考虑到无论是正向dp还是逆向dp,均有值优先于dp[i]先更新(dp[i - 1]和dp[i + 1]会比dp[i]先更新),所以采用两个dp数组的方式,初始值dp[0]=1。每两次dp完后,dp[0]的值及为答案。
AC代码
via Shiroha白羽的博客
题目链接
大致题意
有$(n + m)$个字母A和$(n + m)$个字母B,组成一个长度为 $2*(n + m)$的字符串,并且使得字符串中有$n$个“AB”和$m$个“BA”,求出可能的组合数(mod 1e9+7)
例如,n = 1 m = 2时,可以有这样的字符串(并不是全部的字符串):
ABBABA
ABBBAA
BBABAA
上面三个字符串均满足条件
解题思路
考虑递推,假设已经有一个字符串满足一定的“先决条件”(此处应当理解为数学归纳法,及假设n - 1时满足)
下面考虑==在字符串最后加入一个字符==的情况。仅有两种可能:加A或者加B(这不是白说吗)
但是考虑一下极端情况,我们可以得到一些简单的且明显的条件(N~A~表示已经在字符串中的A个数,N~B~同理)
假如字符串的组成类似这样:
AAAAABBBBBBBB则此字符串中,只能组合出 AB 而不可能组合出 BA
此时,我们假设 $n$ 为 5
而这个字符只能且必定要组合成 5 个AB,也就是说,我们接下来加入字符,只能加入 A 而不能加入 B
此时我们往前推,如果出现了这样一个字符串,则在之前,必定出现如下状态:
AAAAA也即是 5 个A的情况,此时我们可以得到一个确定的关系式:
N~B~ = 0 and N~A~ <= n推广到有B的情况,最优的情况就是所有的B都是用来组成BA,那么可以得到我们真正需要的关系式:
N~A~ - N~B~ <= n同理,相对于 B 而言,我们可以得到
N~B~ - N~A~ <= m合并上述两式
-n < N~A~ - N~B~ <= m所以根据下标为 N~A~ - N~B~ 建立DP数组,下标范围为 -n 到 m (均包含)DP的内容为方案数量(mod 1e9 + 7),递推公式为
$dp[i] = dp[i - 1] + dp [i + 1]$
其中,dp[i - 1]指的是加入一个B(增加一个B使得N~A~ - N~B~变小)。而dp[i + 1]指的是加入一个A
当考虑到无论是正向dp还是逆向dp,均有值优先于dp[i]先更新(dp[i - 1]和dp[i + 1]会比dp[i]先更新),所以采用两个dp数组的方式,初始值dp[0]=1。每两次dp完后,dp[0]的值及为答案。
AC代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <bits/stdc++.h>
using namespace std;
#define MAXN 2100
#define MOD (int)(1e9 + 7)
typedef long long ll;
ll dp[MAXN][2];
int trans(int x) {
return x + 1000;
}
int main() {
#ifdef ACM_LOCAL
freopen("debug.txt", "r", stdin);
#endif
ios::sync_with_stdio(false);
int n, m;
while (cin >> n >> m) {
memset(dp, 0, sizeof(dp));
dp[n][1] = 1;
int cur = 0;
int last = 1;
for (int i = 0; i < 2 * (n + m); i++) {
for (int j = -n; j <= m; j++) {
if (j != -n) {
dp[j + n][cur] += dp[j - 1 + n][last];
dp[j + n][cur] %= MOD;
}
if (j != m) {
dp[j + n][cur] += dp[j + 1 + n][last];
dp[j + n][cur] %= MOD;
}
}
for (int j = -n; j <= m; j++) {
dp[j + n][last] = 0;
}
swap(cur, last);
}
cout << dp[n][last] << endl;
}
return 0;
}
via Shiroha白羽的博客
Educational Codeforces Round 80 D. Minimax Problem——二分+二进制处理
题目链接
题目大意
有n个维度为m的向量,取其中两个进行合并,合并时每个维度取两者之间的较大者,得到的新的向量中,维度值最小者最大为多少
分析
首先最需要注意的是m的取值,m最大只有8,那么我们可以二分答案,对于每一个二分值,进行下面的操作,将整个矩阵的每一个元素,如果这个元素大于二分值,则变成1,反正则变成0,把每一个向量压缩为单个二进制数,这样我们最多只会得到$2^8 = 256$种不同的二进制数,然后暴力的遍历所有可能的二进制数的组合,得到是否满足当前二分值
AC code
via Shiroha白羽的博客
题目链接
题目大意
有n个维度为m的向量,取其中两个进行合并,合并时每个维度取两者之间的较大者,得到的新的向量中,维度值最小者最大为多少
分析
首先最需要注意的是m的取值,m最大只有8,那么我们可以二分答案,对于每一个二分值,进行下面的操作,将整个矩阵的每一个元素,如果这个元素大于二分值,则变成1,反正则变成0,把每一个向量压缩为单个二进制数,这样我们最多只会得到$2^8 = 256$种不同的二进制数,然后暴力的遍历所有可能的二进制数的组合,得到是否满足当前二分值
AC code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include <bits/stdc++.h>
using namespace std;
const int NUM = 3e5 + 100;
int data[NUM][10];
bool check(int value, int n, int m, pair<int, int> &ans) {
map<unsigned, int> s;
for (int i = 0; i < n; ++i) {
unsigned temp = 0;
for (int j = 0; j < m; ++j) {
temp <<= 1u;
temp |= data[i][j] > value;
}
s.insert({temp, i});
}
unsigned tar = -1u >> (sizeof(int) * 8 - m);
for (auto iter1 = s.begin(); iter1 != s.end(); ++iter1) {
for (auto iter2 = iter1; iter2 != s.end(); ++iter2) {
if ((iter1->first | iter2->first) == tar) {
ans.first = iter1->second;
ans.second = iter2->second;
return true;
}
}
}
return false;
}
void solve() {
int n, m;
cin >> n >> m;
int l = INT_MAX, r = 0;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
cin >> data[i][j];
l = min(l, data[i][j]);
r = max(r, data[i][j]);
}
}
int mid, cnt = r - l;
pair<int, int> ans;
while (cnt > 0) {
int step = cnt / 2;
mid = l + step;
if (check(mid, n, m, ans)) {
l = mid + 1;
cnt -= step + 1;
} else
cnt /= 2;
}
cout << ans.first + 1 << " " << ans.second + 1 << endl;
}
signed main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
#ifdef ACM_LOCAL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
long long test_index_for_debug = 1;
char acm_local_for_debug;
while (cin >> acm_local_for_debug) {
cin.putback(acm_local_for_debug);
if (test_index_for_debug > 20) {
throw runtime_error("Check the stdin!!!");
}
auto start_clock_for_debug = clock();
solve();
auto end_clock_for_debug = clock();
cout << "Test " << test_index_for_debug << " successful" << endl;
cerr << "Test " << test_index_for_debug++ << " Run Time: "
<< double(end_clock_for_debug - start_clock_for_debug) / CLOCKS_PER_SEC << "s" << endl;
cout << "--------------------------------------------------" << endl;
}
#else
solve();
#endif
return 0;
}
via Shiroha白羽的博客
记一次 Navicat 连接 MySQL 一直报认证错误(Access denied)
今天一时兴起,想在 WSL2 里下个 MySQL。方法也很简单,直接
本来以为顺风顺水,结果却在 Navicat 连接 MySQL 的操作上出事了
问题
Navicat 无法连接上 MySQL
配置情况
Navicat Premium 15.0.19
MySQL 8.0.22
WSL2(Ubuntu 20)
现状
终端可以通过
终端不可以通过
终端可以通过默认用户连接(默认用户为
Navicat不可以通过直接连接或者通过 ssh 的方式连接,显示密码错误(
Navicat可以通过默认用户连接
经过
尝试1
首先是尝试了百度的结果,重置 MySQL 的 root 账户的密码
因为可以通过
直接进入数据库,然后尝试了下面几行代码
然后,测试
尝试2
后来在MySQL官网找到了重置root密码的方法,然后赶紧拿来测试
官网链接
其中的一点提到
然后,测试
其实觉得挺奇怪的,既然都能重启 MySQL 了,说明你已经拿到这个设备的 root 权限了,为什么不直接用
尝试3
最终我在一份不起眼的博客上找到了解决方案
博客连接
其中提到了一个很重要的命令
然后将方案1的命令稍作改正得到
然后,测试
后续
mysql5.8开始将caching_sha2_password作为默认的身份验证插件,该caching_sha2_password和 sha256_password认证插件提供比mysql_native_password插件更安全的密码加密 ,并 caching_sha2_password提供了比更好的性能sha256_password。由于这些优越的安全性和性能特性 caching_sha2_password它是MySQL 8.0首选的身份验证插件,而且也是默认的身份验证插件而不是 mysql_native_password。此更改会影响服务器和libmysqlclient 客户端库;目前来说和经常使用的客户端软件兼容性不好。
这也是导致目前 Navicat 无法连接到 MySQL 5.8及以后版本的原因。当然如此操作后的影响便是无法直接使用
via Shiroha白羽的博客
今天一时兴起,想在 WSL2 里下个 MySQL。方法也很简单,直接
sudo apt install mysql-server本来以为顺风顺水,结果却在 Navicat 连接 MySQL 的操作上出事了
问题
Navicat 无法连接上 MySQL
配置情况
Navicat Premium 15.0.19
MySQL 8.0.22
WSL2(Ubuntu 20)
现状
终端可以通过
sudo mysql连上 MySQL终端不可以通过
mysql -u root -p的方式连接,显示密码错误(Access denied for user 'root'@'localhost')终端可以通过默认用户连接(默认用户为
/etc/mysql/debian.cnf 文件中的 debian-sys-maint,密码为安装MySQL时随机生成得到的)Navicat不可以通过直接连接或者通过 ssh 的方式连接,显示密码错误(
Access denied for user 'root'@'localhost')Navicat可以通过默认用户连接
经过
尝试1
首先是尝试了百度的结果,重置 MySQL 的 root 账户的密码
因为可以通过
sudo mysql直接进入数据库,也就不需要那么多百度出来的奇奇怪怪的操作了直接进入数据库,然后尝试了下面几行代码
1
2
3
use mysql;
alter user 'root'@'localhost' identified by 'newPassword';
exit
然后,测试
mysql -u root -p连接——失败尝试2
后来在MySQL官网找到了重置root密码的方法,然后赶紧拿来测试
官网链接
其中的一点提到
B.3.3.2.2 Resetting the Root Password: Unix and Unix-Like Systems大致操作就是先终止 MySQL,然后使用 MySQL 的附加参数来设置一个初始化文件,然后使得 MySQL 去运行此文件。
然后,测试
mysql -u root -p连接——失败其实觉得挺奇怪的,既然都能重启 MySQL 了,说明你已经拿到这个设备的 root 权限了,为什么不直接用
sudo mysql 进入直接run这条命令呢?尝试3
最终我在一份不起眼的博客上找到了解决方案
博客连接
其中提到了一个很重要的命令
1
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'insert_password';
This command changes the password for the user root and sets the authentication method to mysql_native_password. This is a traditional method for authentication, and it is not as secure as auth_plugin.其中的
mysql_native_password是所谓的传统验证方案,也就是 Navicat 连接 MySQL 的解决方案然后将方案1的命令稍作改正得到
1
2
3
use mysql;
alter user 'root'@'localhost' identified with mysql_native_password by 'newPassword';
exit
然后,测试
mysql -u root -p连接——成功!后续
mysql5.8开始将caching_sha2_password作为默认的身份验证插件,该caching_sha2_password和 sha256_password认证插件提供比mysql_native_password插件更安全的密码加密 ,并 caching_sha2_password提供了比更好的性能sha256_password。由于这些优越的安全性和性能特性 caching_sha2_password它是MySQL 8.0首选的身份验证插件,而且也是默认的身份验证插件而不是 mysql_native_password。此更改会影响服务器和libmysqlclient 客户端库;目前来说和经常使用的客户端软件兼容性不好。
这也是导致目前 Navicat 无法连接到 MySQL 5.8及以后版本的原因。当然如此操作后的影响便是无法直接使用
sudo mysql的方式连接到数据库,只能通过 mysql -u root -p的传统密码验证的方式来登陆via Shiroha白羽的博客
面试复习(操作系统)
用户态和内核态
● 什么时候从用户态转为内核态 ● 程序在用户态执行时,当需要进行系统调用的时候,或者遇到异常,或者外围设备引发的中断,如文件读取与写入,程序报错,键盘输入,网络操作等行为时,程序会从用户态转为内核态,直到执行此行为结束时,再返回用户态
● 为什么要转至内核态 ● 通过限制用户态的权利,使得有限的系统资源能够受到系统的控制与管理,由系统进行资源的分配
● 用户态和内核态的切换原理 ● 实质上就是中断,保存当前用户态的所有寄存器信息等,然后将代码指针指向中断处理程序
进程和线程
● 区别 ● 进程是相对于操作系统的最小单位,每个进程都有唯一一个 PID 与之对应,每个进程都有独立的内存空间,代码段,数据段。进程之间相互独立且不会相互影响,一个进程可以包含多个线程。CPU在多个进程之间切换时会带来较大的开销。进程可以由CPU单独启动 ● 线程是相对于处理器的最小单位,单个CPU只能同时处理一个线程,相同进程的线程之间共用内存空间,共用代码段和数据段,线程不可以单独执行,线程没有 PID 用于区别,线程出现错误或者异常时会影响此进程内的所有线程,CPU在同一个进程的线程内切换所带来的开销相对较小
● 进程之间的通信 ● 管道(无名和有名管道) ● 消息队列 ● 共享内存 ● 不同的进程可以同时将同一个内存页面映射到自己的地址空间中 ● 信号 ● 套接口(网络)
● 线程之间的通信 ● 锁机制(互斥锁、读写锁) ● wait 和 notify ● violate
● 进程切换代价 ● 切换页目录 ● 切换内核栈 ● 切换上下文
● 线程切换代价 ● 切换内核栈 ● 切换上下文
内存
● 内存寻址是如何实现的 ● 段页式,程序进行分段,包括代码段,数据段等等,每一段再分页,并由 MMU 保存页表 ● MMU 通过页表将逻辑地址转为物理地址
缓存
● 缓存列 ● 每次计算机读取数据放入缓存的单位长度
文件系统
● iNode 结构 ● 文件树结构保存了一个目录的子文件/目录的名称以及对应的 iNode 号码 ● 需要访问文件内容时,需要通过 iNode 号码来获取文件的详细信息 ● iNode 不包含文件名信息,指包含文件的 “元信息”
中断
via Shiroha白羽的博客
用户态和内核态
● 什么时候从用户态转为内核态 ● 程序在用户态执行时,当需要进行系统调用的时候,或者遇到异常,或者外围设备引发的中断,如文件读取与写入,程序报错,键盘输入,网络操作等行为时,程序会从用户态转为内核态,直到执行此行为结束时,再返回用户态
● 为什么要转至内核态 ● 通过限制用户态的权利,使得有限的系统资源能够受到系统的控制与管理,由系统进行资源的分配
● 用户态和内核态的切换原理 ● 实质上就是中断,保存当前用户态的所有寄存器信息等,然后将代码指针指向中断处理程序
进程和线程
● 区别 ● 进程是相对于操作系统的最小单位,每个进程都有唯一一个 PID 与之对应,每个进程都有独立的内存空间,代码段,数据段。进程之间相互独立且不会相互影响,一个进程可以包含多个线程。CPU在多个进程之间切换时会带来较大的开销。进程可以由CPU单独启动 ● 线程是相对于处理器的最小单位,单个CPU只能同时处理一个线程,相同进程的线程之间共用内存空间,共用代码段和数据段,线程不可以单独执行,线程没有 PID 用于区别,线程出现错误或者异常时会影响此进程内的所有线程,CPU在同一个进程的线程内切换所带来的开销相对较小
● 进程之间的通信 ● 管道(无名和有名管道) ● 消息队列 ● 共享内存 ● 不同的进程可以同时将同一个内存页面映射到自己的地址空间中 ● 信号 ● 套接口(网络)
● 线程之间的通信 ● 锁机制(互斥锁、读写锁) ● wait 和 notify ● violate
● 进程切换代价 ● 切换页目录 ● 切换内核栈 ● 切换上下文
● 线程切换代价 ● 切换内核栈 ● 切换上下文
内存
● 内存寻址是如何实现的 ● 段页式,程序进行分段,包括代码段,数据段等等,每一段再分页,并由 MMU 保存页表 ● MMU 通过页表将逻辑地址转为物理地址
缓存
● 缓存列 ● 每次计算机读取数据放入缓存的单位长度
文件系统
● iNode 结构 ● 文件树结构保存了一个目录的子文件/目录的名称以及对应的 iNode 号码 ● 需要访问文件内容时,需要通过 iNode 号码来获取文件的详细信息 ● iNode 不包含文件名信息,指包含文件的 “元信息”
中断
via Shiroha白羽的博客
Codeforces Round#789(Div. 2)
B2. Tokitsukaze and Good 01-String (hard version)
大致题意
有一段 01 组成的字符串,保证长度为偶数
你可以选择一个 0 或者 1,将其变为 1 或者 0
问至少需要操作几次,可以使得所有的 0 或者 1 段都为偶数长度。同时,此时,最少有多少段单独段 0 或 1 段
分析
首先,因为总长度为偶数,所以奇数段一定是成对出现的,可以简单讨论五种情况
● 改变一个奇数段内部,可以生成两个偶数段一个奇数段
● 改变一个偶数段内部,可以生成两个奇数段和一个偶数段
● 改变两个偶数段边缘,可以生成两个奇数段
● 改变两个奇数段边缘,可以生成两个偶数段
● 改变奇偶段边缘,可以交换奇偶关系
这几种方法中,只有改变两个奇数段边缘是有意义的,但是并不一定每次都那么好运。所以必须选择一种方法去将两个离得很远的奇数段靠近
明显只有第一个和最后一个可选,在不产生新的奇数段的前提下改变位置。但是第一个明显有点蠢……因为生成的奇数段在原奇数段内部(仅一个 0 或者 1),所以只能选最后一种方案
所以我们需要选择两个奇数段,然后通过方法五将它们贴近到相邻,然后再用方法四消灭它们,所需要的数量也就是奇数段之间的偶数段个数 + 1
数量解决了,接下来就是分配如何变化使得数量最少了。因为对于每一个奇数段而言,只会改变一个,而对于偶数段而言,两侧边缘都需要发生变化,所以
当奇数段的长度为 1 的时候,变化此奇数段,当偶数段长度为 2 的时候,左右两侧都变化此偶数段。然后再统计不同的奇偶段数量即可
AC Code
via Shiroha白羽的博客
B2. Tokitsukaze and Good 01-String (hard version)
大致题意
有一段 01 组成的字符串,保证长度为偶数
你可以选择一个 0 或者 1,将其变为 1 或者 0
问至少需要操作几次,可以使得所有的 0 或者 1 段都为偶数长度。同时,此时,最少有多少段单独段 0 或 1 段
分析
首先,因为总长度为偶数,所以奇数段一定是成对出现的,可以简单讨论五种情况
● 改变一个奇数段内部,可以生成两个偶数段一个奇数段
● 改变一个偶数段内部,可以生成两个奇数段和一个偶数段
● 改变两个偶数段边缘,可以生成两个奇数段
● 改变两个奇数段边缘,可以生成两个偶数段
● 改变奇偶段边缘,可以交换奇偶关系
这几种方法中,只有改变两个奇数段边缘是有意义的,但是并不一定每次都那么好运。所以必须选择一种方法去将两个离得很远的奇数段靠近
明显只有第一个和最后一个可选,在不产生新的奇数段的前提下改变位置。但是第一个明显有点蠢……因为生成的奇数段在原奇数段内部(仅一个 0 或者 1),所以只能选最后一种方案
所以我们需要选择两个奇数段,然后通过方法五将它们贴近到相邻,然后再用方法四消灭它们,所需要的数量也就是奇数段之间的偶数段个数 + 1
数量解决了,接下来就是分配如何变化使得数量最少了。因为对于每一个奇数段而言,只会改变一个,而对于偶数段而言,两侧边缘都需要发生变化,所以
当奇数段的长度为 1 的时候,变化此奇数段,当偶数段长度为 2 的时候,左右两侧都变化此偶数段。然后再统计不同的奇偶段数量即可
AC Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include "bits/stdc++.h"
using namespace std;
#define int long long
void solve() {
int _;
cin >> _;
for (int ts = 0; ts < _; ++ts) {
int n;
cin >> n;
string str;
str.resize(n);
cin >> str;
vector<int> st;
char last = -1;
for (int i = 0; i < n; ++i) {
if (str[i] == last) {
st.back()++;
} else {
st.push_back(1);
last = str[i];
}
}
int isOdd = 0, ans = 0;
for (int i = 0; i < st.size(); ++i) {
if (st[i] % 2) {
isOdd = !isOdd;
if (st[i] == 1) st[i] = 0;
} else if (isOdd) {
if (st[i] == 2) st[i] = 0;
}
ans += isOdd;
}
int ls = -1, cnt = 0;
for (int i = 0; i < st.size(); ++i) {
if (st[i] == 0) continue;
if (ls != (i % 2)) {
ls = i % 2;
cnt++;
}
}
cout << ans << ' ' << max(1LL, cnt) << endl;
}
}
signed main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
#ifdef ACM_LOCAL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
signed localTestCount = 1, localReadPos = (signed) cin.tellg();
char localTryReadChar;
do {
if (localTestCount > 20)
throw runtime_error("Check the std input!!!");
auto startClockForDebug = clock();
solve();
auto endClockForDebug = clock();
cerr << "Test " << localTestCount++ << " Run Time: "
<< double(endClockForDebug - startClockForDebug) / CLOCKS_PER_SEC << "s" << endl;
// cout << "Test " << localTestCount << " successful" << endl;
// cout << "--------------------------------------------------" << endl;
} while (localReadPos != cin.tellg() && cin >> localTryReadChar && localTryReadChar != '$' &&
cin.putback(localTryReadChar));
#else
solve();
#endif
return 0;
}
via Shiroha白羽的博客
Java Script 的 null 和 undefined 随想
有些时候感觉一些语言里看起来很蠢的设计,实际上却能解决一些很有意思的场景。比如 JavaScript 的 null 和 undefined,虽然看起来都是表示空的意思,但是实际上却解决了“没有这个值”,“这个值为空”这样两种语义。在缓存穿透的问题上,如果 redis、memcached 等数据库也有这样一层设计等话,是不是就能解决 null 穿透问题了呢
via Shiroha白羽的博客
有些时候感觉一些语言里看起来很蠢的设计,实际上却能解决一些很有意思的场景。比如 JavaScript 的 null 和 undefined,虽然看起来都是表示空的意思,但是实际上却解决了“没有这个值”,“这个值为空”这样两种语义。在缓存穿透的问题上,如果 redis、memcached 等数据库也有这样一层设计等话,是不是就能解决 null 穿透问题了呢
via Shiroha白羽的博客
Educational Codeforces Round#153 (Div. 2)
A. Not a Substring
大致题意
需要构建一个只有小括号构成的字符串,既满足括号匹配,同时不存在一个子串等同于给出的一个字符串
思路
实际上很简单,只需要取
AC code
B. Fancy Coins
大致题意
有 $a1$ 个 $1$ 元,$a2$ 个 $k$ 元,同时你可以“借来”无限量的 $1$ 元和 $k$ 元,问组成 $m$ 元最多需要借多少硬币
思路
简单卡一下边界,多一个 $k$ 元和少一个 $k$ 元的两种情况考虑一下即可,比较简单
AC code
C. Game on Permutation
大致题意
有一个数组,开始位置可以是任意的一个下标,每次可以移动到当前位置左边的任意一个值小于当前的位置。
两个人依次操作,谁最后无法进行操作了,谁胜利,问放在哪些位置,可以保证第二个开始操作的胜利
思路
假如说我摆放在一个位置,然后可以通过 $3$ 个依次操作达到最终无法移动(例如 $a \rightarrow b \rightarrow c \rightarrow d$),那么此时应该说第二个移动的人胜利
但是这个操作是可跳过的,因为你可以移动 $3$ 次,那么就必然可以一次移动到底,因为一定也符合题意,那么第一个移动的人为什么要遵循一个个移动呢,他完全可以直接 $a \rightarrow c$,然后第二个操作的人只能移动到 $d$,然后输了游戏
所以必须卡在一些只能移动一次的地方,否则就有可乘之机。
那么就必须保证选择的点满足
● 大于左边最小的值
● 小于左边之前确认的满足条件的点
AC code
via Shiroha白羽的博客
A. Not a Substring
大致题意
需要构建一个只有小括号构成的字符串,既满足括号匹配,同时不存在一个子串等同于给出的一个字符串
思路
实际上很简单,只需要取
()()()() 模式 和 (((()))) 这两种即可,因为这两种模式的唯一相同的子串就只有一对 (),而若需要一个满足括号匹配的字符串,那么必然存在 (),故这两种模式就可以应对所有情况AC code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void solve() {
int _;
cin >> _;
for (int ts = 0; ts < _; ++ts) {
string str, ans;
cin >> str;
ans.resize(str.size() << 1);
for (int i = 0; i < str.size(); ++i) ans[i] = '(';
for (int i = 0; i < str.size(); ++i) ans[i + str.size()] = ')';
if (strstr(ans.c_str(), str.c_str()) == nullptr) {
cout << "YES" << endl;
cout << ans << endl;
continue;
}
for (int i = 0; i < str.size(); ++i) ans[i * 2] = '(';
for (int i = 0; i < str.size(); ++i) ans[i * 2 + 1] = ')';
if (strstr(ans.c_str(), str.c_str()) == nullptr) {
cout << "YES" << endl;
cout << ans << endl;
continue;
}
cout << "NO" << endl;
}
}
B. Fancy Coins
大致题意
有 $a1$ 个 $1$ 元,$a2$ 个 $k$ 元,同时你可以“借来”无限量的 $1$ 元和 $k$ 元,问组成 $m$ 元最多需要借多少硬币
思路
简单卡一下边界,多一个 $k$ 元和少一个 $k$ 元的两种情况考虑一下即可,比较简单
AC code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void solve() {
int _;
cin >> _;
for (int ts = 0; ts < _; ++ts) {
int m, k, a1, a2;
cin >> m >> k >> a2 >> a1;
m -= min(m / k, a1) * k;
if (m <= a2) {
cout << 0 << endl;
continue;
}
int ls = (m - a2) / k;
int ans = ls + (m - a2 - ls * k);
if (m - (ls + 1) * k >= 0) ans = min(ans, ls + 1);
cout << ans << endl;
}
}
C. Game on Permutation
大致题意
有一个数组,开始位置可以是任意的一个下标,每次可以移动到当前位置左边的任意一个值小于当前的位置。
两个人依次操作,谁最后无法进行操作了,谁胜利,问放在哪些位置,可以保证第二个开始操作的胜利
思路
假如说我摆放在一个位置,然后可以通过 $3$ 个依次操作达到最终无法移动(例如 $a \rightarrow b \rightarrow c \rightarrow d$),那么此时应该说第二个移动的人胜利
但是这个操作是可跳过的,因为你可以移动 $3$ 次,那么就必然可以一次移动到底,因为一定也符合题意,那么第一个移动的人为什么要遵循一个个移动呢,他完全可以直接 $a \rightarrow c$,然后第二个操作的人只能移动到 $d$,然后输了游戏
所以必须卡在一些只能移动一次的地方,否则就有可乘之机。
那么就必须保证选择的点满足
● 大于左边最小的值
● 小于左边之前确认的满足条件的点
AC code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void solve() {
int _;
cin >> _;
for (int ts = 0; ts < _; ++ts) {
int n, ans = 0, curMin = INT_MAX, curMax = INT_MAX;
cin >> n;
for (int i = 0; i < n; ++i) {
int tmp;
cin >> tmp;
if (tmp < curMin) curMin = tmp;
else if (tmp > curMin && tmp < curMax) {
ans++;
curMax = tmp;
}
}
cout << ans << endl;
}
}
via Shiroha白羽的博客
CodeTON Round 6 (Div. 2)
A. MEXanized Array
大致题意
构造一个数组,其长度为 $n$,最大值为 $x$,$MEX$ 为 $k$,问这个数组的所有值的和最大是多少
思路
简单题,在 $k > x + 1$ 或 $n < k$ 的场景下无解(不可能构造一个 $MEX$ 不可达的数组),然后就随便构造就行了,保证 $MEX$ 之后,剩下所有值取最大就行
AC code
B. Friendly Arrays
大致题意
给出两个数组 $a, b$,允许选择任意次的 $b$ 数组中的任意一个 $b_j$,然后让 $\forall i \in [1, len(a)], a_i = a_i | b_j$,问最终得到的数组 $a$ 中所有的异或和最大和最小的可能
思路
某个比特为是 $1$ 的情况下,在奇数个值异或和的结果则也是 $1$,而偶数个则为 $0$。而或运算可以让 $a$ 数组的每一个值的某些个位都变成 $1$。基于此,只需要关心 $a$ 的长度即可,若 $a$ 为奇数,那么选尽可能多的 $b$ 使得每个位都尽可能是 $1$,反之则尽可能不选,这样才能达到最大,同理可以得到最小的方案
AC code
C. Colorful Table
大致题意
有一个数组 $a$,长度为 $n$,然后有一个对应的矩阵 $b$,为 $n \times n$,其每一个位置的值 $b_{i,j}=min(a_i, a_j)$
问对于每个数字 $x$,在矩阵 $b$,中能够找到对应一个最小的矩形,此矩形包含了所有出现 $x$ 的位置,求出这个矩形的大小
思路
对于任意一个值,假定其第一次在 $a$ 中出现的位置为 $i$,它第一次出现在 $b$ 地点一定是 $b_{i,i}$,同时其最后一次在矩阵中的位置一定是 $b_{j,j}$,其中 $j$ 是在数组 $a$,中出现的,最后一个比当前值更大的下标
根据上面的规律,可以求出实际上每个值的位置,一定可以包裹比他大的那个值对应的矩阵,所以只需要根据值的大小排序一下他们在数组中第一次出现的位置,和最后一次出现的位置,然后从大到小遍历,保证小的值的区间能够覆盖到大的值的区间即可
AC code
D. Prefix Purchase
大致题意
有一个初始数组,每一个值都是 $0$,每次你可以选择花费 $c_i$ 元,使得这个数组前 $i$ 个元素加一,最多只能花费 $k$ 元,问能够得到最大字典序的数组是什么
思路
首先需把 $c$ 的值进行单调递增栈处理一下,毕竟价格相同或更低的同时 $i$ 更大肯定有优势
回到题目中的字典序,意味着只有越前面的值越大即可,所以要尽可能满足最前面的值最大,所以直接把 $k$ 丢给处理后的第一个值,看看最多第一个值可以到多少
处理完成第一个值后,那就意味着后面无论怎么贪心,第一个值一定要达到这个,否则肯定不如现在更好。另外,对于字典序而言,约前面的值价值越高,所以要尽可能让前面的值大,贪心一下即可
AC code
via Shiroha白羽的博客
A. MEXanized Array
大致题意
构造一个数组,其长度为 $n$,最大值为 $x$,$MEX$ 为 $k$,问这个数组的所有值的和最大是多少
思路
简单题,在 $k > x + 1$ 或 $n < k$ 的场景下无解(不可能构造一个 $MEX$ 不可达的数组),然后就随便构造就行了,保证 $MEX$ 之后,剩下所有值取最大就行
AC code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void solve() {
int _;
cin >> _;
for (int ts = 0; ts < _; ++ts) {
int n, k, x;
cin >> n >> k >> x;
if (k > x + 1 || n < k) {
cout << -1 << endl;
continue;
}
int sum = 0;
for (int i = 0; i < k; ++i) sum += i;
for (int i = k; i < n; ++i) sum += (x == k ? x - 1 : x);
cout << sum << endl;
}
}
B. Friendly Arrays
大致题意
给出两个数组 $a, b$,允许选择任意次的 $b$ 数组中的任意一个 $b_j$,然后让 $\forall i \in [1, len(a)], a_i = a_i | b_j$,问最终得到的数组 $a$ 中所有的异或和最大和最小的可能
思路
某个比特为是 $1$ 的情况下,在奇数个值异或和的结果则也是 $1$,而偶数个则为 $0$。而或运算可以让 $a$ 数组的每一个值的某些个位都变成 $1$。基于此,只需要关心 $a$ 的长度即可,若 $a$ 为奇数,那么选尽可能多的 $b$ 使得每个位都尽可能是 $1$,反之则尽可能不选,这样才能达到最大,同理可以得到最小的方案
AC code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void solve() {
int _;
cin >> _;
for (int ts = 0; ts < _; ++ts) {
int n, m;
cin >> n >> m;
int sa = 0, sb = 0, tmp;
for (int i = 0; i < n; ++i) {
cin >> tmp;
sa ^= tmp;
}
for (int i = 0; i < m; ++i) {
cin >> tmp;
sb |= tmp;
}
cout << (n % 2 ? sa : sa & ~sb) << ' ' << (n % 2 ? sa | sb : sa) << endl;
}
}
C. Colorful Table
大致题意
有一个数组 $a$,长度为 $n$,然后有一个对应的矩阵 $b$,为 $n \times n$,其每一个位置的值 $b_{i,j}=min(a_i, a_j)$
问对于每个数字 $x$,在矩阵 $b$,中能够找到对应一个最小的矩形,此矩形包含了所有出现 $x$ 的位置,求出这个矩形的大小
思路
对于任意一个值,假定其第一次在 $a$ 中出现的位置为 $i$,它第一次出现在 $b$ 地点一定是 $b_{i,i}$,同时其最后一次在矩阵中的位置一定是 $b_{j,j}$,其中 $j$ 是在数组 $a$,中出现的,最后一个比当前值更大的下标
根据上面的规律,可以求出实际上每个值的位置,一定可以包裹比他大的那个值对应的矩阵,所以只需要根据值的大小排序一下他们在数组中第一次出现的位置,和最后一次出现的位置,然后从大到小遍历,保证小的值的区间能够覆盖到大的值的区间即可
AC code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void solve() {
int _;
cin >> _;
for (int ts = 0; ts < _; ++ts) {
int n, k;
cin >> n >> k;
vector<bool> flag(k, false);
vector<pair<int, int>> data(k, {-1, -1});
for (int i = 0; i < n; ++i) {
int tmp;
cin >> tmp;
data[tmp - 1].first == -1 ? data[tmp - 1].first = data[tmp - 1].second = i : data[tmp - 1].second = i;
flag[tmp - 1] = true;
}
for (int i = k - 2; i >= 0; --i) {
data[i].first = !flag[i] ? data[i + 1].first : (data[i + 1].first != -1 ? min(data[i].first, data[i + 1].first) : data[i].first);
data[i].second = !flag[i] ? data[i + 1].second : (data[i + 1].first != -1 ? max(data[i].second, data[i + 1].second) : data[i].second);
}
for (int i = 0; i < k; ++i)
cout << (!flag[i] ? 0 : (data[i].second - data[i].first + 1) + (data[i].second - data[i].first + 1)) << ' ';
cout << endl;
}
}
D. Prefix Purchase
大致题意
有一个初始数组,每一个值都是 $0$,每次你可以选择花费 $c_i$ 元,使得这个数组前 $i$ 个元素加一,最多只能花费 $k$ 元,问能够得到最大字典序的数组是什么
思路
首先需把 $c$ 的值进行单调递增栈处理一下,毕竟价格相同或更低的同时 $i$ 更大肯定有优势
回到题目中的字典序,意味着只有越前面的值越大即可,所以要尽可能满足最前面的值最大,所以直接把 $k$ 丢给处理后的第一个值,看看最多第一个值可以到多少
处理完成第一个值后,那就意味着后面无论怎么贪心,第一个值一定要达到这个,否则肯定不如现在更好。另外,对于字典序而言,约前面的值价值越高,所以要尽可能让前面的值大,贪心一下即可
AC code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void solve() {
int _;
cin >> _;
for (int ts = 0; ts < _; ++ts) {
int n, k;
cin >> n;
vector<pair<int, int>> data;
for (int i = 0; i < n; ++i) {
int tmp;
cin >> tmp;
while (!data.empty() && data.back().first >= tmp) data.pop_back();
data.emplace_back(tmp, i);
}
data.emplace_back(INT_MAX, n - 1);
cin >> k;
vector<int> ans(data.size());
ans[0] = k / data.front().first;
k %= data.front().first;
for (int i = 1; i < data.size(); ++i) {
int diff = data[i].first - data[i - 1].first;
if (k < diff) {
break;
}
ans[i] = min(ans[i - 1], k / diff);
k -= diff * ans[i];
}
int cur = 0;
for (int i = 0; i < n; ++i) {
while (data[cur].second < i) cur++;
cout << ans[cur] << " \n"[i == n - 1];
}
}
}
via Shiroha白羽的博客
Codeforces Round 901 (Div. 2)
最近双十一加班严重,难得有一个完整的周末假期,来写点题稍微恢复一下脑子吧
A. Jellyfish and Undertale
大致题意
有一个炸弹,有倒计时在缓慢减少,你有 $n$ 个道具,每次你可以花费 1s 的时间来使用,使得倒计时增加 $v_i$
秒,但是由于一些故障,每次加完后,不能超过上限 $a$,否则就会变成 $a$。问最多可以让炸弹坚持到几秒
思路
注意操作可以是任何时候进行的,所以当每次只剩下 1s 的时候操作就是最好的,不然就炸了,因为是先完成加时间,再扣除当前操作的
1s,故只需要考虑每个都在 1s 的时候操作即可,即对每个值取 $min(v_i, a - 1)$ 然后求和就行了
AC code
B. Jellyfish and Game
大致题意
A 有 $n$ 个苹果,每个都有重量,B 有 $m$ 个,每次交换,A 或者 B 可以选择自己的一个苹果给对方,同时从对方那边拿来一个苹果,两人都希望自己的苹果重量之和最大,问依次交换
$x$ 次后,$A$ 的苹果重量之和是多少
思路
模拟就行了,说白了交换了两次之后,就是纯粹的互换相同的那两个苹果,只需要考虑最开始的两次即可
AC code
C. Jellyfish and Green Apple
大致题意
有 $n$ 个苹果,要平均分给 $m$ 个人,每次可以把一片苹果平均切成两份,问至少要切几刀才能平分
思路
其实是一个小数二进制问题,根据小数二进制方式去解决,从高位开始,一步步减去需要的苹果块,每一步减完之后,就可以将剩下来的苹果块全部对切开,因为不会再用到更大的苹果块了
AC code
D. Jellyfish and Mex
大致题意
有一个数组,每次可以从中删除一个值,然后得到对应的 $mex$,问直到整个数组被完整删除后,所有得到的 $mex$,相加最小可能是多少
思路
举个例子来看
$mex$ 都是 $0$。但是直接删除 $0$ 的代价非常大,因为前两次删除都会导致代价为 $6$ 的 $mex$。这是因为 $0$ 出现了 $3$ 次。如果我们先删除
$2$,然后再删除 $0$ 那么就会发现,只需要额外增加 $2$ 的代价,就能让后面删除 $0$ 的两次操作的代价从 $6$ 减少到 $2$。
所以可以得到,我们尽量应该删除越少越小的值,即如果值增加的情况下,数量还不减少,那么肯定没有必要优先做删除了,可以等 $mex$ 变成
$0$ 之后再动手。而对于这些值,当然也应该从较大者开始删除,这样可以尽快减小 $mex$
的值(因为在上面的前提下,最大值的出现次数一定比较小值少)但是不能每个值都要操作,例如例子中的 $1$
就是不需要操作的,即使其恰好在这条单调栈上,即需要从一个序列中取出最优的子序列
我们考虑最多会出现多少个这样的需要考虑的数字,假设刚好递减的情况,且数量为 $n$,那么总占用的数字数量就是 $n * (n + 1) /
2$。故对于长度为 $5000$ 的数组,实际上 $n < 100$。即 $n^2$ 暴力去找子序列是可以的
AC code
via Shiroha白羽的博客
最近双十一加班严重,难得有一个完整的周末假期,来写点题稍微恢复一下脑子吧
A. Jellyfish and Undertale
大致题意
有一个炸弹,有倒计时在缓慢减少,你有 $n$ 个道具,每次你可以花费 1s 的时间来使用,使得倒计时增加 $v_i$
秒,但是由于一些故障,每次加完后,不能超过上限 $a$,否则就会变成 $a$。问最多可以让炸弹坚持到几秒
思路
注意操作可以是任何时候进行的,所以当每次只剩下 1s 的时候操作就是最好的,不然就炸了,因为是先完成加时间,再扣除当前操作的
1s,故只需要考虑每个都在 1s 的时候操作即可,即对每个值取 $min(v_i, a - 1)$ 然后求和就行了
AC code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define int long long
void solve() {
int _;
cin >> _;
for (int ts = 0; ts < _; ++ts) {
int a, b, n;
cin >> a >> b >> n;
for (int i = 0; i < n; ++i) {
int tmp;
cin >> tmp;
b += min(tmp, a - 1);
}
cout << b << endl;
}
}
B. Jellyfish and Game
大致题意
A 有 $n$ 个苹果,每个都有重量,B 有 $m$ 个,每次交换,A 或者 B 可以选择自己的一个苹果给对方,同时从对方那边拿来一个苹果,两人都希望自己的苹果重量之和最大,问依次交换
$x$ 次后,$A$ 的苹果重量之和是多少
思路
模拟就行了,说白了交换了两次之后,就是纯粹的互换相同的那两个苹果,只需要考虑最开始的两次即可
AC code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#define int long long
void solve() {
int _;
cin >> _;
for (int ts = 0; ts < _; ++ts) {
int n, m, k;
cin >> n >> m >> k;
vector<int> a(n), b(m);
for (auto &i: a) cin >> i;
for (auto &i: b) cin >> i;
auto sort_all = [&]() {
sort(a.begin(), a.end());
sort(b.begin(), b.end());
};
sort_all();
if (a.front() < b.back()) {
swap(a.front(), b.back());
}
if (k >= 2) {
sort_all();
if (b.front() < a.back()) {
swap(b.front(), a.back());
}
if (k % 2) {
sort_all();
if (a.front() < b.back()) {
swap(a.front(), b.back());
}
}
}
int tot = 0;
for (auto &i : a) tot += i;
cout << tot << endl;
}
}
C. Jellyfish and Green Apple
大致题意
有 $n$ 个苹果,要平均分给 $m$ 个人,每次可以把一片苹果平均切成两份,问至少要切几刀才能平分
思路
其实是一个小数二进制问题,根据小数二进制方式去解决,从高位开始,一步步减去需要的苹果块,每一步减完之后,就可以将剩下来的苹果块全部对切开,因为不会再用到更大的苹果块了
AC code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#define int long long
void solve() {
int _;
cin >> _;
for (int ts = 0; ts < _; ++ts) {
int n, m;
cin >> n >> m;
n %= m;
if (n == 0) {
cout << 0 << endl;
continue;
}
// check m is or not the power of 2
int tmp = gcd(m, n);
tmp = m / tmp;
bool flag = true;
while (tmp != 1) {
if (tmp % 2 == 1) {
flag = false;
break;
}
tmp >>= 1;
}
if (!flag) {
cout << -1 << endl;
continue;
}
int ans = n;
n <<= 1;
while (n) {
n %= m;
ans += n;
n <<= 1;
}
cout << ans << endl;
}
}
D. Jellyfish and Mex
大致题意
有一个数组,每次可以从中删除一个值,然后得到对应的 $mex$,问直到整个数组被完整删除后,所有得到的 $mex$,相加最小可能是多少
思路
举个例子来看
0 0 0 1 1 2 3 3 3 4 4 4 5 5 5首先要让 $mex$ 尽可能小,那么就应该尽量挑小的开始删除,显然,如果我把 $0$ 删除完那就会使得后面所有的操作都是无代价的,即随便删的
$mex$ 都是 $0$。但是直接删除 $0$ 的代价非常大,因为前两次删除都会导致代价为 $6$ 的 $mex$。这是因为 $0$ 出现了 $3$ 次。如果我们先删除
$2$,然后再删除 $0$ 那么就会发现,只需要额外增加 $2$ 的代价,就能让后面删除 $0$ 的两次操作的代价从 $6$ 减少到 $2$。
所以可以得到,我们尽量应该删除越少越小的值,即如果值增加的情况下,数量还不减少,那么肯定没有必要优先做删除了,可以等 $mex$ 变成
$0$ 之后再动手。而对于这些值,当然也应该从较大者开始删除,这样可以尽快减小 $mex$
的值(因为在上面的前提下,最大值的出现次数一定比较小值少)但是不能每个值都要操作,例如例子中的 $1$
就是不需要操作的,即使其恰好在这条单调栈上,即需要从一个序列中取出最优的子序列
我们考虑最多会出现多少个这样的需要考虑的数字,假设刚好递减的情况,且数量为 $n$,那么总占用的数字数量就是 $n * (n + 1) /
2$。故对于长度为 $5000$ 的数组,实际上 $n < 100$。即 $n^2$ 暴力去找子序列是可以的
AC code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#define int long long
void solve() {
int _;
cin >> _;
for (int ts = 0; ts < _; ++ts) {
int n;
cin >> n;
map<int, int> mp;
for (int i = 0; i < n; ++i) {
int tmp;
cin >> tmp;
mp[tmp]++;
}
int mex = 0;
while (mp.count(mex)) mex++;
if (mex == 0) {
cout << 0 << endl;
continue;
}
vector<pair<int, int>> data;
for (auto &iter: mp) {
if (iter.first > mex) break;
if (data.empty() || data.back().second > iter.second)
data.emplace_back(iter);
}
reverse(data.begin(), data.end());
vector<int> dp(data.size());
for (int i = 0; i < data.size(); ++i) {
dp[i] = (data[i].second - 1) * mex + data[i].first;
for (int j = 0; j < i; ++j)
dp[i] = min(dp[i], (data[i].second - 1) * data[j].first + data[i].first + dp[j]);
}
cout << dp.back() << endl;
}
}
via Shiroha白羽的博客
Codeforces Round 907 (Div. 2)
A. Sorting with Twos
大致题意
每次可以从前往后选择 $2^x$ 个值,每个值减少一,问进行无数次操作后,是否可能让整个数组变成无递减
思路
简单题,只要原数组中,那些盲区仍然保持非递减即可。例如 $[3, 4]$ 这种区间,要么一起减少要么一起不减少
AC code
B. Deja Vu
大致题意
给出两个数组,对于第一个数组 $a$ 的每一个值,进行如下操作:
● 从前往后遍历数组 $b$
● 若 $a_i \space mod \space 2^{b_i} = 0$ 则 $a_i \leftarrow a_i + 2^{b_i - 1}$
求最终数组
思路
数据量很大,但是有技巧
因为一旦满足 $a_i \space mod \space 2^{b_i} = 0$ 之后,会加上的是 $2^{b_i - 1}$。
这也就意味着,如此操作之后,其必然可以被 $2^{b_i - 1}$ 整除,且最大只能被它整除了。
也就是说,每次能够加上的值一定是不断变小的
题目中给出的 $b_i \in [1, 30]$ 所以其实第二个数组最多只能有 30 个有效值。处理之后暴力即可
AC code
C. Smilo and Monsters
大致题意
有一堆怪物窝,每个怪物窝里有一定数量的怪物。你有一个累计的技能点数,初始值为 0,每次可以选择不同的技能
● 找一个怪物窝,打死里面的一只怪物,积累一点技能点数
● 找一个怪物窝,里面的怪物数量不大于你的技能点数,消耗全部的技能点数释放大招,消灭这个窝里的全部怪物
问最少需要几次操作
思路
对于一个窝而言,只需要打死里面的一半的怪物,再加上一次使用技能,就可以实现打败这个窝了,此时成本为 $\left \lceil \frac{x}{2} \right \rceil + 1$
如果有两个窝,假设都这样操作,那么代价就是 $\left \lceil \frac{x}{2} \right \rceil + \left \lceil \frac{y}{2} \right \rceil + 2$
假如我将小一点的那个窝全部一只只打死,然后打几只大窝里的怪,再对大一点对窝释放大招,也就是只使用一次技能,代价就是 $x + (\left \lceil \frac{y+x}{2} \right \rceil - x) + 1 = \left \lceil \frac{y+x}{2} \right \rceil + 1$
显然,后者价值更高,所以要考虑按照后者的操作进行,即多用小窝的怪刷技能点,然后对大窝放技能。用双指针做就行了
AC code
D. Suspicious logarithms
大致题意
定义两个函数,$f(x) = y, g(x) = z$,满足 $2^y \leq x, y^z \leq z$,且 $y, z$ 都尽可能大
给出一个区间,求 $\sum_{i=l}^{r} g(i)$
思路
虽然看起来很难,但是观察可以发现,$y \in [1, 64]$,而 $z \in [0, 10]$,所以只需要枚举所有的 $y, z$ 即可
AC code
via Shiroha白羽的博客
A. Sorting with Twos
大致题意
每次可以从前往后选择 $2^x$ 个值,每个值减少一,问进行无数次操作后,是否可能让整个数组变成无递减
思路
简单题,只要原数组中,那些盲区仍然保持非递减即可。例如 $[3, 4]$ 这种区间,要么一起减少要么一起不减少
AC code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void solve() {
int _;
cin >> _;
for (int ts = 0; ts < _; ++ts) {
int n;
cin >> n;
vector<int> data(n);
for (auto& i: data) cin >> i;
bool flag = true;
pair<int, int> arr[4] = {{2, 3}, {4, 7}, {8, 15}, {16, 31}};
for (auto [l, r]: arr) {
for (; l < min(r, n - 1); ++l) {
if (data[l] > data[l + 1]) {
flag = false;
break;
}
}
}
cout << (flag ? "YES" : "NO") << endl;
}
}
B. Deja Vu
大致题意
给出两个数组,对于第一个数组 $a$ 的每一个值,进行如下操作:
● 从前往后遍历数组 $b$
● 若 $a_i \space mod \space 2^{b_i} = 0$ 则 $a_i \leftarrow a_i + 2^{b_i - 1}$
求最终数组
思路
数据量很大,但是有技巧
因为一旦满足 $a_i \space mod \space 2^{b_i} = 0$ 之后,会加上的是 $2^{b_i - 1}$。
这也就意味着,如此操作之后,其必然可以被 $2^{b_i - 1}$ 整除,且最大只能被它整除了。
也就是说,每次能够加上的值一定是不断变小的
题目中给出的 $b_i \in [1, 30]$ 所以其实第二个数组最多只能有 30 个有效值。处理之后暴力即可
AC code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#define int long long
void solve() {
int _;
cin >> _;
for (int ts = 0; ts < _; ++ts) {
int n, q;
cin >> n >> q;
vector<int> data(n), query;
query.reserve(30);
for (auto& i: data) cin >> i;
for (int i = 0; i < q; ++i) {
int tmp;
cin >> tmp;
if (i == 0 || tmp < query.back()) query.push_back(tmp);
}
for (auto& i: data) for (long long j: query) if (i % (1 << j) == 0) i += 1 << j - 1;
for (int i = 0; i < n; ++i) cout << data[i] << " \n"[i == n - 1];
}
}
C. Smilo and Monsters
大致题意
有一堆怪物窝,每个怪物窝里有一定数量的怪物。你有一个累计的技能点数,初始值为 0,每次可以选择不同的技能
● 找一个怪物窝,打死里面的一只怪物,积累一点技能点数
● 找一个怪物窝,里面的怪物数量不大于你的技能点数,消耗全部的技能点数释放大招,消灭这个窝里的全部怪物
问最少需要几次操作
思路
对于一个窝而言,只需要打死里面的一半的怪物,再加上一次使用技能,就可以实现打败这个窝了,此时成本为 $\left \lceil \frac{x}{2} \right \rceil + 1$
如果有两个窝,假设都这样操作,那么代价就是 $\left \lceil \frac{x}{2} \right \rceil + \left \lceil \frac{y}{2} \right \rceil + 2$
假如我将小一点的那个窝全部一只只打死,然后打几只大窝里的怪,再对大一点对窝释放大招,也就是只使用一次技能,代价就是 $x + (\left \lceil \frac{y+x}{2} \right \rceil - x) + 1 = \left \lceil \frac{y+x}{2} \right \rceil + 1$
显然,后者价值更高,所以要考虑按照后者的操作进行,即多用小窝的怪刷技能点,然后对大窝放技能。用双指针做就行了
AC code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#define int long long
void solve() {
int _;
cin >> _;
for (int ts = 0; ts < _; ++ts) {
int n;
cin >> n;
vector<int> data(n);
for (auto& i: data) cin >> i;
sort(data.begin(), data.end());
int l = 0, r = n - 1, x = 0, ans = 0;
while (l <= r) {
if (x >= data[r]) {
data[r] = 0;
x = 0;
--r;
++ans;
} else {
const int tmp = l == r ? (x + data[l] + 1) / 2 - x : min(data[l], data[r] - x);
x += tmp;
ans += tmp;
data[l] -= tmp;
if (!data[l]) ++l;
}
}
cout << ans << endl;
}
}
D. Suspicious logarithms
大致题意
定义两个函数,$f(x) = y, g(x) = z$,满足 $2^y \leq x, y^z \leq z$,且 $y, z$ 都尽可能大
给出一个区间,求 $\sum_{i=l}^{r} g(i)$
思路
虽然看起来很难,但是观察可以发现,$y \in [1, 64]$,而 $z \in [0, 10]$,所以只需要枚举所有的 $y, z$ 即可
AC code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#define int long long
void solve() {
map<pair<int, int>, int> mp;
for (int i = 2; i < 60; ++i) {
const __int128 ml = 1ll << i, mr = 1ll << i + 1;
__int128 base = 1;
for (int j = 0; j <= 10; ++j) {
if (base >= mr) break;
if (base * i <= ml) {
base *= i;
continue;
}
mp.insert({{max(ml, base), min(mr, base * i) - 1}, j});
base *= i;
}
}
constexpr int mod = 1e9 + 7;
int _;
cin >> _;
for (int ts = 0; ts < _; ++ts) {
int l, r;
cin >> l >> r;
int ans = 0;
for (auto& [fst, snd]: mp) {
const int len = min(fst.second, r) - max(fst.first, l) + 1;
if (len <= 0) continue;
ans = (ans + len * snd % mod) % mod;
}
cout << ans << endl;
}
}
via Shiroha白羽的博客