本菜鸟又在CF Div2水了一次,这次的5道题只做出了两道,Div2的C,D(Div1的A,B),不错的题目,可惜当时只想到了一半半,要继续修炼呀。
题意是这样的,给定两个串s1,s2,s1可以使用多次,拼成s1s1s1…s1这种串,然后去掉任意多个字母能变成s2,问说最少用多少个s1。
暴力的思路比较好想,在s1,s2上设立两个指针,都是单向的,相应进行匹配,如果s1处的指针走满后,就另起一个,但这样的复杂度是O(ans*|s1|),主要的开销在于s1上的指针每次都要走来走去的,因为字典就a…z这26种,每次找过的a,又得重新再找一遍,太冗余了,可以用dp(i, j)记录从i…len(s1)这个串中,最左边的字母j的位置,这样就可以快速定位i指针的位置了。预处理dp(i, j)的时间复杂度是O(26*|s1|),总的复杂度为O(26*|s1| + |s2|),这样的复杂度就可以AC了,也可以这样优化,建立26个有序表,然后二分快速定位相应的位置,这样的话可以把复杂度降为O(|s1| + |s2| * log|s2|),补充一句,题解说可以用B树优化。代码如下:
#include <iostream>
#include <algorithm>
#include <string>
#include <set>
#include <vector>
#include <map>
using namespace std;
const int MAX = 1e4 + 5;
string s1, s2;
int d[MAX][26]; //d[i][j]表示从s1[i],s1[i+1],...,s1[len(s1)]中最左边的i在哪里
int main()
{
cin >> s1 >> s2;
int i;
int len1 = s1.length();
int len2 = s2.length();
memset(d, -1, sizeof(d));
for(i = len1 - 1; i >= 0; i--)
{
for(int j = 0; j < 26; j++) d[i][j] = d[i + 1][j];
d[i][s1[i] - 'a'] = i;
}
i = 0;
int ans = 0, j = 0;
while(j < len2)
{
if(d[i][s2[j] - 'a'] != -1)
{
i = d[i][s2[j] - 'a'] + 1;
j++;
}
else
{
if(i == 0) break;
ans++;
i = 0;
}
}
if(j < len2) cout << "-1";
else cout << ans + 1;
}
题意是这样的,N个整数组成一个序列,用a1, a2, …, an表示,对于每个每个位置i,求出i右边,最远的j使得aj < ai。题解的方法很巧妙,建立数组m[],m[i]表示从ai到an中的最小值,则m[i]是个递减数组,然后枚举的时候,二分逼近那个最远的边界就可以了,复杂度是O(N*logN),有人提到先对数组排序后,然后枚举,并保存最远的位置,也是个不错的方法,但要注意数组中的值可能相等,复杂度也是O(N*logN)。前者方法的代码如下:
#include <iostream>
#include <string>
#include <set>
#include <vector>
#include <map>
using namespace std;
const int MAX = 1e5 + 5;
int v[MAX], r[MAX], m[MAX], n;
int main()
{
cin >> n;
for(int i = 1; i <= n; i++)
cin >> v[i];
r[n] = n; m[n] = v[n];
for(int i = n - 1; i >= 1; i--)
{
m[i] = min(m[i + 1], v[i]);
int low = i, high = n;
while(low <= high)
{
int mid = (low + high) / 2;
if(m[mid] < v[i]) low = mid + 1;
else high = mid - 1;
}
if(high >= i) r[i] = high;
else r[i] = i;
}
for(int i = 1; i <= n; i++)
cout << r[i] - i - 1 << " ";
}