D. Trees and Segments
D. Trees and Segments
The teachers of the Summer Informatics School decided to plant trees in a row, and it was decided to plant only oaks and firs. To do this, they made a plan, which can be represented as a binary string of length . If , then the -th tree in the row should be an oak, and if , then the -th tree in the row should be a fir.
The day of tree planting is tomorrow, and the day after tomorrow an inspector will come to the School. The inspector loves nature very much, and he will evaluate the beauty of the row as follows:
- First, he will calculate as the maximum number of consecutive oaks in the row (the maximum substring consisting of zeros in the plan ). If there are no oaks in the row, then .
- Then, he will calculate as the maximum number of consecutive firs in the row (the maximum substring consisting of ones in the plan ). If there are no firs in the row, then .
- Finally, he will calculate the beauty of the row as for some — the inspector's favourite number.
The teachers know the value of the parameter , but for security reasons they cannot tell it to you. They only told you that is an integer from to .
Since the trees have not yet been planted, the teachers decided to change the type of no more than trees to the opposite (i.e., change from to or from to in the plan) in order to maximize the beauty of the row of trees according to the inspector.
For each integer from to answer the following question independently:
- What is the maximum beauty of the row of trees that the teachers can achieve by changing the type of no more than trees if the inspector's favourite number is equal to ?
Input
The first line contains a single integer () — the number of test cases.
The first line of each test case contains two integers and (, ) — the number of trees in the row and the maximum number of changes.
The second line contains a string of length , consisting of zeros and ones — the plan description.
It is guaranteed that the sum of all values for all test cases does not exceed .
Output
For each test case, print integers, the -th () of which is the maximum beauty of the row of trees after no more than changes if is used to calculate the beauty.
Example
input
5 3 0 111 4 1 0110 5 0 10000 6 2 101101 7 1 0001101
output
3 3 3 4 5 7 9 5 9 13 17 21 6 9 13 17 21 25 7 10 13 17 21 25 29
Note
In the first test case no changes are allowed, so and always hold. Thus, regardless of the value of , the beauty of the row of trees will be .
In the second test case for the teachers can, for example, change the plan to (by changing ), and for — to (by changing ). In this case, the beauty of the row for each is calculated as follows:
- For : , . The beauty of the row is .
- For : , . The beauty of the row is .
- For : , . The beauty of the row is .
- For : , . The beauty of the row is .
It can be shown that the changes described above are optimal for all from to .
解题思路
题解有点看不懂,大概说一下我的做法吧。
现在要求的是对于每个,的最大值是多少,这取决于和分别是多少。可以知道当对字符串修改后,连续最长的一段,和连续最长的一段分别在左部分和右部分(或者是右部分和左部分),并且没有交集。
而最长一段的长度,所以想到枚举最长一段所有可能的长度,当固定后枚举找到最大能取多少。这样就能在修改之后连续最长的一段是的所有方案中,确定的最大值,进而使得最大(对于固定的和)。同理,最长一段的长度,同样枚举最长一段所有可能的长度,当固定后枚举找到最大能取多少。
最终就会得到最多个二元组,这些二元组涵盖了所有能使得取最大值的情况,接下来我们只需要枚举所有的,固定的值后再枚举所有的二元组,计算取最大值,这一步的时间复杂度是。
上面就是大致的思路,下面给出具体做法,这里只给出枚举分别找到最大的方法,另外的枚举分别找到最大的做法也是类似的,只需把字符串中的变成,变成,然后用同样的方法即可。
先枚举最长一段的长度,然后在字符串中找到所有长度为的连续子串,来作为最长的一段。因此需要先判定能否将该子串全部变成,即子串中的个数是否不超过。如果那么我们就无法通过不超过步的修改使得整个子串变成。否则,假设子串的左右端点为和,满足,那么我们就看看字符串及中连续最长的是多少,记作。最后枚举完所有长度为的子串,把最大的那个作为。
上面做法的伪代码如下:
1 for (int len = 0; len <= n; len++) { // 枚举连续最长的0的长度 2 int l1 = -1; 3 for (int l = 1; l + len - 1 <= n; l++) { // 枚举所有长度为len的子串左端点l 4 int r = l + len - 1; // 子串右端点r 5 int cnt = 0; 6 for (int i = l; i <= r; i++) { 7 if (str[i] == '1') cnt++; // 统计子串中1的个数 8 } 9 if (cnt > k) continue; // 1的个数超过k,无法把子串修改为全0,枚举下一个子串 10 int s = k = cnt; // 剩余能修改的次数 11 int t = 0; 12 // 找到[1,l-1]中连续最长的一段1 13 for (int i = 1; i < l; i++) { 14 for (int j = 0; j <= s; j++) { 15 if (str[i] == '1') f[i][j] = f[i - 1][j] + 1; 16 else if (j) f[i][j] = f[i - 1][j - 1] + 1; 17 else f[i][j] = 0; 18 } 19 t = max(t, f[i][s]); 20 } 21 // 找到[r+1,n]中连续最长的一段1 22 for (int i = n; i > r; i--) { 23 for (int j = 0; j <= s; j++) { 24 if (str[i] == '1') g[i][j] = g[i + 1][j] + 1; 25 else if (j) g[i][j] = g[i + 1][j - 1] + 1; 26 else g[i][j] = 0; 27 } 28 t = max(t, f[i][s]); 29 } 30 l1 = max(l1, t); 31 } 32 if (l1 != -1) p.push_back({len, l1}); // l1==-1说明字符串中不存在长度为len的连续一段0 33 }
可以看到时间复杂度是。其中上面代码中找到和中连续最长的一段用到了动态规划。
实际上上面的做法是可以通过预处理优化到的,首先求子串中的个数可以用前缀和优化到,找到和中连续最长的一段也可以通过动态规划预处理所有的情况来优化到。
先定义状态表示前缀中以第个字符作为连续最长一段的右端点,且最多修改次得到的最大长度。状态转移方程为
再定义状态表示只考虑前个字符,且最多修改次得到的所有方案中,连续一段的最大长度。根据第个字符是否在最长的连续一段中进行状态划分,因此状态转移方程就是
这样对于任意的前缀以及最多能修改的次数,就能通过查询来得到。
同理定义状态表示后缀中以第个字符作为连续最长一段的左端点,且最多修改次得到的最大长度。状态转移方程为
再定义状态表示只考虑中的字符,且最多修改次得到的所有方案中,连续一段的最大长度。根据第个字符是否在最长的连续一段中进行状态划分,因此状态转移方程就是
这样对于任意的前缀以及最多能修改的次数,就能通过查询来得到。
上面dp的时间复杂度是。
AC代码如下,时间复杂度为:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 3010; 5 6 int n, m; 7 char str[N]; 8 int s[N]; 9 int f[N][N], g[N][N], h[N][N]; 10 vector<vector<int>> p; 11 12 void get(int op) { 13 if (op) { 14 for (int i = 1; i <= n; i++) { 15 str[i] ^= 1; 16 } 17 } 18 for (int i = 1; i <= n; i++) { 19 s[i] = s[i - 1]; 20 if (str[i] == '1') s[i]++; 21 } 22 for (int i = 0; i <= n + 1; i++) { 23 for (int j = 0; j <= m; j++) { 24 f[i][j] = g[i][j] = h[i][j] = 0; 25 } 26 } 27 for (int i = 1; i <= n; i++) { 28 for (int j = 0; j <= m; j++) { 29 if (str[i] == '1') h[i][j] = h[i - 1][j] + 1; 30 else if (j) h[i][j] = h[i - 1][j - 1] + 1; 31 else h[i][j] = 0; 32 f[i][j] = max(f[i - 1][j], h[i][j]); 33 } 34 } 35 for (int i = n; i; i--) { 36 for (int j = 0; j <= m; j++) { 37 if (str[i] == '1') h[i][j] = h[i + 1][j] + 1; 38 else if (j) h[i][j] = h[i + 1][j - 1] + 1; 39 else h[i][j] = 0; 40 g[i][j] = max(g[i + 1][j], h[i][j]); 41 } 42 } 43 for (int len = 0; len <= n; len++) { 44 int t = -1; 45 for (int i = 1; i + len - 1 <= n; i++) { 46 int j = i + len - 1, cnt = s[j] - s[i - 1]; 47 if (cnt <= m) t = max({t, f[i - 1][m - cnt], g[j + 1][m - cnt]}); 48 } 49 if (t == -1) break; 50 if (!op) p.push_back({len, t}); 51 else p.push_back({t, len}); 52 } 53 } 54 55 void solve() { 56 scanf("%d %d %s", &n, &m, str + 1); 57 p.clear(); 58 get(0), get(1); 59 for (int i = 1; i <= n; i++) { 60 int ret = 0; 61 for (auto &q : p) { 62 ret = max(ret, i * q[0] + q[1]); 63 } 64 printf("%d ", ret); 65 } 66 printf("\n"); 67 } 68 69 int main() { 70 int t; 71 scanf("%d", &t); 72 while (t--) { 73 solve(); 74 } 75 76 return 0; 77 }
参考资料
Codeforces Round #893 (Div. 2) Editorial:https://codeforces.com/blog/entry/119398
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/17636147.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效