「PMOI-5」送分题/Yet Another Easy Strings Merging
题目背景
本题征集假做法和 hack 数据,如果您用假做法 AC 了,欢迎私信出题人提供 hack。
信息可能有冗余。
------command_block 《考前小贴士》
djy 在看 P8001,看错题了,很自闭,然后就有了这个题。
题目描述
给定 n n n 个 01 串,每次你可以从某个串开头移除一个字符并把剩下的字符串 加入一个新串 S S S 的末尾。最大化 S S S 中相邻两个字符相同的对数。
例如你有 1010 111 两个串,如果你移除第一个串的第一个字符,则 010 被加入到 S S S 中。
串可以重复使用。
输入格式
第一行一个正整数 n n n 表示串的个数。
接下来 n n n 行,每行一个 01 字符串。
输出格式
一行一个整数表示答案。
样例 #1
样例输入 #1
1
1100
样例输出 #1
4
样例 #2
样例输入 #2
5
10010
10000
01110
111111
000000
样例输出 #2
48
提示
【样例解释】
依次取走第一个字符, S S S 的变化过程为 100->10000->100000,答案为 4 4 4。
【数据范围】
记 ∣ s ∣ |s| ∣s∣ 为字符串 s s s 的长度, s i s_i si 为第 i i i 个字符串 。
本题采用捆绑测试。
- Subtask 1(30 pts): n , ∑ ∣ s i ∣ ≤ 11 n,\sum|s_i|\le 11 n,∑∣si∣≤11;
- Subtask 2(30 pts): n , ∑ ∣ s i ∣ ≤ 10 3 n,\sum|s_i|\le 10^3 n,∑∣si∣≤103;
- Subtask 3(30 pts): n , ∑ ∣ s i ∣ ≤ 10 5 n,\sum|s_i|\le 10^5 n,∑∣si∣≤105;
- Subtask 4(10 pts):无特殊限制。
对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 10 6 1\le n\le 10^6 1≤n≤106, n ≤ ∑ ∣ s i ∣ ≤ 10 6 n\le \sum |s_i|\le 10^6 n≤∑∣si∣≤106, ∀ i ∈ [ 1 , n ] \forall i\in [1,n] ∀i∈[1,n], ∣ s i ∣ ≥ 1 |s_i|\ge 1 ∣si∣≥1。
简化版
问题比较复杂,我先看一个简单的问题。
n个字符串,每个字符串只会是00、01、10、11。将其串联起来,问最多有多少相邻字符相同。
性质一 :如果存在两个不相邻的00,调整成挨在一起是不劣解。
令第一个00的前后字符分别是c1,c2,令另外一个00的前后字符是c3、c4。
将另外一个00插入到c1后面:左边相同相邻字符增加1。如果c3c4是00,右边相同相邻字符减少1。如果c3,c4是11,则右边相同相邻字符数量不变。如果c3c4一个0,一个1,则右边相同相邻字符数量减少1。综上所述,不连续的00挨着一起是不劣解。同理,相邻的11也调整在一起。
性质二 :如果存在10或01,所有的00和11都可以串联在10(01)上。
性质三 :10和01交叉出现,多的开头。不失一般性,令10的数量大于等于01的数量,如果10的数量和01的数量相等或多一,可以将10和01全部串联起来。m个字符串串联起来,共有m-1个相同相邻字符。
总结 :我们把首位相同的串联起来。令c1是01的数量,c2是10的数量,不失一般性,假定c1 <=c2。00的数量c3,11数量c4。
将c1 和01和min(c2,c1+1)和10串联。如果c2不为0,将00和11全部串联起来;如果c2为0,则00和00串联,11和11串联。
注意:00和11内部有相同相邻字符。
本题
证明
将个字符串的后缀,从长到短处理。
性质一 :如果当前字符串以0结尾,则如果存在00可选,选择00是不劣解。
令某最优解,当前字符后面是a,某00前后分别是b,c。
无论a是0还是1,左边都增加一个相同相邻字符。
bc为00、01、10右边都减少一个相同相邻字符,为11,右边相邻字符不变。
性质二 :排除性质一后,如果当前字符串以0结尾,则如果存在01可选,选择01是不劣解。
如果a是0,要么符合性质一,要么符合性质二。故只需要证明a为1。
01变0011,左边增加两个相同相邻字符,右边无论如何都不可能减少3个或更多相同相邻字符。
性质三 :排除性质一性质二后,如果当前字符串以0结尾,则如果存在11可选,选择11是不劣解。
排除性质一后,a一定为1。
01变0111,左边增加1个相同相邻字符。如果bc为01、10、11,右边相同相邻字符减少1,bc为00,右边相同相邻字符数量不变。
小结一 :如果当前字符串以0,结尾依次选择00、01、11、10。上述证明不考虑00、11内部相同的字符。
小结二 :如果当前字符串以1结尾,则优先选择11、10、00、01。
性质五 :如果有多个00(11)选择任意一个,因为按上述规则会优先选择完所有00(11)。
猜想一:如果有多个01(10)任意选择一个。证明方法未知。
解法
long long ans 记录答案。
最后计算各字符串内部的相同相邻字符数,并加到ans上。a[i]记录s[i...]的相邻字符数。a[i] = a[i+1]+(s[i] = = s[i+1])。
Do1函数分别枚举00、01、10、11开头。
v[i]记录首尾分别为00、01、10、11的信息,包括:第i个字符串,此字符串后缀的起始下标。
end记录,前一个字符,2表示空。
如果end是2,则依次选择v[begin] 、v[0]、v[1]、v[2]、v[3]。
如果end是0,则依次选择v[0,1,3,2]。
如果end是1,则依次选择v[3,2,0,1]。
vsort记录三个下标选择顺序。
vsort[2][0] = begin。
将各字符出掉首字符后,入栈(封装成函数)。
end =2。
如果v[i]全部为空,结束循环;否则按顺序选择一个字符串。
ans = max(ans,curans)
如果选择的是v[sel],则:
curans += (end == sel/2)
end = sel%2
v[sel]栈顶元素的下一个后缀入栈
v[sel]出栈
代码
核心代码
cpp
#include <iostream>
#include <sstream>
#include <vector>
#include<map>
#include<unordered_map>
#include<set>
#include<unordered_set>
#include<string>
#include<algorithm>
#include<functional>
#include<queue>
#include <stack>
#include<iomanip>
#include<numeric>
#include <math.h>
#include <climits>
#include<assert.h>
#include<cstring>
#include <bitset>
using namespace std;
template<class T = int>
vector<T> Read() {
int n;
scanf("%d", &n);
vector<T> ret(n);
for(int i=0;i < n ;i++) {
cin >> ret[i];
}
return ret;
}
template<class T = int>
vector<T> Read(int n) {
vector<T> ret(n);
for (int i = 0; i < n; i++) {
cin >> ret[i];
}
return ret;
}
template<class T1,class T2>
void ReadTo(pair<T1, T2>& pr) {
cin >> pr.first >> pr.second;
}
class Solution {
public:
long long Ans(vector<string>& strs) {
long long ans = 0;
for (int i = 0; i < 4; i++) {
ans = max(ans, Do(strs, i));
}
for (const auto& s : strs) {
int pre = 0;
for (int j = s.length() - 2; j >= 1; j--) {
pre += (s[j] == s[j + 1]);
ans += pre;
}
}
return ans;
}
long long Do(vector<string>& strs, int begin) {
long long ret = 0;
int vSort[3][5] = { {0,1,3,2,0},{3,2,0,1,0},{0,0,1,2,3} };
vSort[2][0] = begin;
int end = 2;
vector<pair<int, int>> v[4];
auto Add = [&](int i, int j) {
if (j >= strs[i].length()) { return; }
const int inx = (strs[i][j] - '0') * 2 + (strs[i].back() - '0');
v[inx].emplace_back(i, j);
};
for (int i = 0; i < strs.size(); i++) {
Add(i, 1);
}
while (v[0].size() || v[1].size() || v[2].size() || v[3].size())
{
for (auto sel : vSort[end]) {
if (v[sel].size()) {
ret += (end == sel / 2);
end = sel % 2;
auto [i, j] = v[sel].back();
v[sel].pop_back();
Add(i, j + 1);
break;
}
}
}
return ret;
}
};
int main() {
#ifdef _DEBUG
freopen("a.in", "r", stdin);
#endif // DEBUG
auto strs = Read<string>();
#ifdef _DEBUG
/*printf("N=%d,K=%d,T=%d,", N, K,T);*/
Out(strs, "strs=");
#endif
auto res = Solution().Ans(strs);
cout << res << std::endl;
return 0;
}
单元测试
cpp
vector<string> strs;
TEST_METHOD(TestMethod11)
{
strs = {"1100"};
auto res = Solution().Ans(strs);
AssertEx(4LL, res);
}
TEST_METHOD(TestMethod12)
{
strs = { "00","01","10","11"};
auto res = Solution().Ans(strs);
AssertEx(2LL, res);
}
TEST_METHOD(TestMethod13)
{
strs = {"10010","10000","01110","111111","000000" };
auto res = Solution().Ans(strs);
AssertEx(48LL, res);
}
扩展阅读
| 我想对大家说的话 |
|---|
| 工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。 |
| 学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作 |
| 有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注 |
| 闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
| 子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
| 如果程序是一条龙,那算法就是他的是睛 |
| 失败+反思=成功 成功+反思=成功 |
视频课程
先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。