今天是魔怔的一天,被一道题目整魔怔了,不过还好最后还是做出来了。
这道题目整了挺久的,终于整出来了。
之前一直在想A - B=C该怎么计数,然后怎么想怎么超时(虽然样例一定能过,但是基本用的都是比较“朴素”的算法,时间复杂度都接近O(n^2),不可取,一定会超时),于是我就想着把这个表达式变形一下入手。因为C是已知的,且C只有1个,那么就可以以此为切入点。
A - B = C → A - C = B → B + C = A
我每个都试过了,然后从A - C = B找到了方案。
因为位置不同但是答案是C的同样两个数是会被计数的,不算重复,因此我们可以直接通过map映射来计数(这道题有个2^30,原本想着可能是hash的,既然如此那就用map吧)。首先可以直接把输入的原数据给计数一遍,我们就得到了一个类似桶一样的离散计数(应该是离散吧?毕竟数据是跳着记的,但数据总量至多2 * 10^5)。因为我们的目标是A - C = B,那么可以得到的答案就是B的计数。为什么是B的计数?首先不同位置相同数不算重复,可以直接计数,其次B在实际上代表了"A - C = B"的可达成方案的数字。
比如:
4 1
1 1 2 3
那么我们的map计数: map[1] = 2, map[2] = 1, map[3] = 1
因为C = 1,那么a[0 ... n] - C = {0, 0, 1, 2} = B
那么B的集合中的元素就是可以达成方案的数,再通过计数map[B[0 ... n]]就可以得到最终的方案总数量。
sum = map[0] + map[0] + map[1] + map[2] = 0 + 0 + 2 + 1 = 3,总共3个方案,与样例答案相同。
这道题真的想了很久,最后还是从我以前无聊写的一个用户交互的窗口应用上找到灵感的。下面上代码。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
ios::sync_with_stdio(false);
ll n, C;
ll groups = 0;
cin >> n;
cin >> C;
int a[n];
map<int, int> mp; //用于计数各种数有几个
for(int i = 0;i < n;i ++)
{
cin >> a[i];
mp[a[i]] += 1; //计数,这样就不用两层去数"有几对"了
}
for(int i = 0;i < n;i ++)
{
int num = a[i] + C; //利用A - B = C -> B + C = A
groups += mp[num];
}
cout << groups << endl;
return 0;
}
这道题,一看"前后缀匹配",应该是用kmp的题目了。
可是我kmp不太会。
简简单单地把之前做的kmp模板搬了下来,然后稍加修改调试调试,就变成了这样:
#include <bits/stdc++.h>
using namespace std;
int nextval[1000001];
void GetNext(string T)
{
nextval[0] = -1;
int j = -1;
for(int i = 1;i < T.size();i ++)
{
while(j != -1 && T[i] != T[j + 1])
{
j = nextval[j];
}
if(T[i] == T[j + 1]) j ++;
nextval[i] = j;
}
}
int KMP(string S, string T)
{
int lenS = S.size(), lenT = T.size();
int lenm = lenS < lenT ? lenS : lenT;
int j = -1;
GetNext(T);
for(int i = lenS - lenm;i < lenS;i ++)
{
while(j != -1 && S[i] != T[j + 1])
{
j = nextval[j];
}
if(S[i] == T[j + 1])
{
j ++;
}
}
return j;
}
int main()
{
ios::sync_with_stdio(false);
int n;
cin >> n;
// cout << n << endl;
// getchar();
string res;
string nexts;
cin >> res;
// cout << res << endl;
for(int i = 0;i < n - 1;i ++)
{
cin >> nexts;
// cout << nexts << endl;
GetNext(nexts);
int pos = KMP(res, nexts);
// cout << pos << endl;
nexts.erase(0, pos + 1);
res = res + nexts;
}
cout << res;
return 0;
}
思路是:kmp找到最大前后缀匹配长度,然后把单词重复的前缀删掉,加到句子上去。我认为这个思路一点问题也没有,而且样例也都是过的。于是我就交了上去... ...
结果
TLE了。
说明这个代码还有可以优化的地方。
尝试了一波吸氧,发现还是
还是老老实实去优化吧。
然后我就想到,C++调用函数好像也要一些时间,所以我就把KMP和算NEXT的部分都放到了主函数里面。
然后我就换了它们的位置,变成了下面这样:
#include <bits/stdc++.h>
using namespace std;
int nextval[1000001];
//void GetNext(string T)
//{
// nextval[0] = -1;
// int j = -1;
// for(int i = 1;i < T.size();i ++)
// {
// while(j != -1 && T[i] != T[j + 1])
// {
// j = nextval[j];
// }
// if(T[i] == T[j + 1]) j ++;
// nextval[i] = j;
// }
//}
//
//int KMP(string S, string T)
//{
// int lenS = S.size(), lenT = T.size();
// int lenm = lenS < lenT ? lenS : lenT;
// int j = -1;
// GetNext(T);
// for(int i = lenS - lenm;i < lenS;i ++)
// {
// while(j != -1 && S[i] != T[j + 1])
// {
// j = nextval[j];
// }
// if(S[i] == T[j + 1])
// {
// j ++;
// }
// }
// return j;
//}
int main()
{
ios::sync_with_stdio(false);
int n;
cin >> n;
// cout << n << endl;
// getchar();
string S;
string T;
cin >> S;
// cout << res << endl;
for(int i = 0;i < n - 1;i ++)
{
cin >> T;
nextval[0] = -1;
int j = -1;
for(int i = 1;i < T.size();i ++)
{
while(j != -1 && T[i] != T[j + 1])
{
j = nextval[j];
}
if(T[i] == T[j + 1]) j ++;
nextval[i] = j;
}
// cout << nexts << endl;
// GetNext(nexts);
int lenS = S.size(), lenT = T.size();
int lenm = lenS < lenT ? lenS : lenT;
j = -1;
// GetNext(T);
for(int i = lenS - lenm;i < lenS;i ++)
{
while(j != -1 && S[i] != T[j + 1])
{
j = nextval[j];
}
if(S[i] == T[j + 1])
{
j ++;
}
}
// cout << pos << endl;
T.erase(0, j + 1);
S = S + T;
}
cout << S;
return 0;
}
样例也能过,就交上去了,顺带吸氧。
结果还是这样子:
说明优化还不够彻底。
于是乎我又进行了一点点的优化
#include <bits/stdc++.h>
using namespace std;
int nextval[1000001];
//void GetNext(string T)
//{
// nextval[0] = -1;
// int j = -1;
// for(int i = 1;i < T.size();i ++)
// {
// while(j != -1 && T[i] != T[j + 1])
// {
// j = nextval[j];
// }
// if(T[i] == T[j + 1]) j ++;
// nextval[i] = j;
// }
//}
//
//int KMP(string S, string T)
//{
// int lenS = S.size(), lenT = T.size();
// int lenm = lenS < lenT ? lenS : lenT;
// int j = -1;
// GetNext(T);
// for(int i = lenS - lenm;i < lenS;i ++)
// {
// while(j != -1 && S[i] != T[j + 1])
// {
// j = nextval[j];
// }
// if(S[i] == T[j + 1])
// {
// j ++;
// }
// }
// return j;
//}
int main()
{
ios::sync_with_stdio(false);
int n;
cin >> n;
// cout << n << endl;
// getchar();
string S;
string T;
cin >> S;
int lenS = S.size(); //提前计算减少计算量
// cout << res << endl;
for(int i = 0;i < n - 1;i ++)
{
cin >> T;
int lenT = T.size();
nextval[0] = -1;
int j = -1;
for(int i = 1;i < lenT;i ++)
{
while(j != -1 && T[i] != T[j + 1])
{
j = nextval[j];
}
if(T[i] == T[j + 1]) j ++;
nextval[i] = j;
}
// cout << nexts << endl;
// GetNext(nexts);
int lenm = lenS < lenT ? lenS : lenT;
j = -1;
// GetNext(T);
for(int i = lenS - lenm;i < lenS;i ++)
{
while(j != -1 && S[i] != T[j + 1])
{
j = nextval[j];
}
if(S[i] == T[j + 1])
{
j ++;
}
}
// cout << pos << endl;
T.erase(0, j + 1);
S = S + T;
lenS += T.size(); //确保S长度更新
}
cout << S;
return 0;
}
把长度计算给放在了外面,希望这样能减少计算,结果还是TLE。
我又优化了很久很久... ...
最后咨询了一下群友和同学,在查阅了相关kmp的资料后,又重新开始套模板写,写出了这个玩意:
#include <bits/stdc++.h>
#define MaxSize 1000001
using namespace std;
string chs[MaxSize], S;
int nextval[MaxSize];
int ii;
//模板
void GetNext(string T)
{
int len = T.size();
int i = 0;
int j = -1;
nextval[0] = -1;
while(i < len)
{
if(j == -1 || T[i] == T[j])
{
i ++;
j ++;
if(T[i] != T[j])
{
nextval[i] = j;
}
else
{
nextval[i] = nextval[j];
}
}
else
{
j = nextval[j];
}
}
}
int KMP(string S, string T, int pos)
{
GetNext(T);
int i = pos;
int j = 0;
int lenS = S.size();
int lenT = T.size();
while(i < lenS)
{
if(j == -1 || S[i] == T[j])
{
i ++;
j ++;
}
else
{
j = nextval[j];
}
}
return j;
}
int main()
{
ios::sync_with_stdio(false);
int n;
cin >> n;
for(int i = 0;i < n;i ++)
{
cin >> chs[i];
}
S = chs[0];
for(ii = 1;ii < n;ii ++)
{
int posi = 0 > S.size() - chs[ii].size() ? 0 : S.size() - chs[ii].size();
//此处的posi就是匹配的位置
int pos = KMP(S, chs[ii], posi);
//这里是匹配的最长位置
chs[ii].erase(0, pos);
S += chs[ii];
}
cout << S;
return 0;
}
结果还是TLE。
"会不会是erase太慢了?"我这么想到,于是就打算用朴素的方法试一下。
for(int i = pos;i < chs[ii].size();i ++)
{
S.push_back(chs[ii][i]);
}
结果还是,TLE... ...
于是乎我又很仔细地在看哪里可以优化... ...
似乎调用地址更快一点?
所以我就把
int KMP(string S, string T, int pos)
换成了
int KMP(string &S, string &T, int pos)
我靠,然后就过了。
<----
不得不说,这差的时间不是一点半点... ...
做出来的那一个瞬间是非常爽的,做了10个小时差不多了这道题,都快魔怔了,终于给我搞出来了。
明天又有新的任务了,听说比较难,得好好学学... ...
就今天这劲头,应该可以学会的吧。