比赛链接:传送门
题目大意:
一只青蛙在长度为N的字符串上跳跃,“R”可以跳上去,“P”不可以跳上去。
字符串是环形的,N-1和0相连。
青蛙的跳跃距离K的取值范围是[1, N-1],选定K之后不可改变。
要求青蛙最后能跳回起点(起点可以是0-N-1的任意一个位置),问K的取值有多少种选择。
3≤N≤105。
思路:
考虑到如果gcd(N, K) = g,则从起点开始跳的话,所有经过的点都是g的倍数,而且每个g的倍数都会经过。
所以只要考虑从任意一个点i开始,步长为g地跳,能不遇见"P"而跳到N+i的位置的话,那么这个K可以选。
直接模拟的话就O(N2logN)了,考虑优化。
因为对于确定的g,对应的有很多个K,而这些K的选与不选是确定的,所以考虑枚举g(其实就是N的约数),对每个g预处理出它是否能满足题意地完成条件。
没记错的话约数的数量应该是logN级别的,所以环形dp预处理的复杂度为O(NlogN)。
然后枚举一遍K,更新答案就可以了。
复杂度O(NlogN + NlogN)。
代码:
#include <bits/stdc++.h> using namespace std;
const int MAX_N = 1e5 + ; int N;
char S[MAX_N]; vector <int> factor;
void getFactors(int N)
{
factor.clear();
for (int i = ; i <= N/i; i++) {
if (N%i == ) {
factor.push_back(i);
if (i*i != N)
factor.push_back(N/i);
}
}
sort(factor.begin(), factor.end());
} bool f[MAX_N << ][];
bool can_jump[MAX_N];
void dp()
{
int cnt = factor.size();
memset(f, false, sizeof f);
for (int i = ; i <= *N; i++) {
for (int j = ; j < cnt; j++) {
int tmp = factor[j];
if (S[(i-)%N] == 'P')
f[i][j] = false;
else if (S[(i-)%N] == 'R') {
if (i-tmp <= )
f[i][j] = true;
else
f[i][j] = f[i-tmp][j];
}
}
} memset(can_jump, false, sizeof can_jump);
for (int i = N+; i <= *N; i++) {
for (int j = ; j < cnt; j++) {
int tmp = factor[j];
if (f[i][j])
can_jump[tmp] = true;
}
}
} inline int gcd(int a, int b)
{
return a%b ? gcd(b, a%b) : b;
} int main()
{
scanf("%s", S);
N = strlen(S);
getFactors(N);
dp(); int ans = ;
for (int k = ; k <= N-; k++) {
int g = gcd(k, N);
if (can_jump[g])
ans++;
}
cout << ans << endl;
return ;
}