部分内容参考了该题解。
大的来了。
题目的条件可以看作是存在整数 l 满足 3l≤k 且前 2l 个数构成回文串,因此问题可以看作是统计长度不超过 n 的序列中存在一个足够短的偶数长度回文前缀的序列数量。
考虑 dp,设 fi 表示长度为 2i 且最短偶回文串恰好为整个串的序列的数量。长度为 2i 的回文串一共有 mi 个,但是其中某些回文串可能有更短的偶回文前缀。假设最短回文前缀的长度为 2j,则序列的前 2j 位有 fj 种填写方案,为了将其补全成长度为 2i 的回文串,中间还有 i−2j 个位置可以随便填,因此该类的不合法方案数就是 fjmi−2j 种。
把上面东西合起来写可以得到递推式 fi=mi−j=1∑⌊2i⌋fjmi−2j。
然后考虑枚举最短回文前缀的长度 2l。序列的总长度为 k 时因为要求 3l≤k,而前 2l 位有 fl 种填法,后 k−2l 位随便填,方案数就是 flmk−2l。
因此总的答案就是 i=1∑⌊3n⌋fik=3i∑nmk−2i,直接算时间复杂度是 O(n2) 的,考虑优化。答案内层是一个等比数列求和的形式,因此还可以被表示为 i=1∑⌊3n⌋fi1−mmi−mn−2i+1 的形式,也就是 1−m1(i=1∑⌊3n⌋fimi−i=1∑⌊3n⌋fimn−2i+1),求和部分可以优化到线性时间复杂度。
(我想到这里就不会了)
记 S(n,t)=i=1∑nfimti,则答案可以表示为 1−mS(⌊3n⌋,1)−mn+1S(⌊3n⌋,−2) 的形式。
先考虑怎么优化递推的时间复杂度。已知 fl=ml−i=1∑⌊2l⌋fiml−2i,则把递推公式直接带入到 S(∗,∗) 中,可得 S(n,t)=l=1∑n(ml−i=1∑⌊2l⌋fiml−2i)mtl=i=1∑nmlmtl−l=1∑ni=1∑⌊2l⌋fiml−2imtl=m(t+1)l−l=1∑ni=1∑⌊2l⌋fim(t+1)l−2i。
现在考虑怎么优化算 l=1∑ni=1∑⌊2l⌋fim(t+1)l−2i 的值。考虑交换求和顺序,可得 i=1∑⌊2n⌋l=2i∑nfim(t+1)l−2i=i=1∑⌊2n⌋fim−2il=2i∑nm(t+1)l。换元记 q=mt+1,则内层求和可以看作是 l=2i∑nql=1−qq2i−qn+1,因此双重和式的值为 i=1∑⌊2n⌋fim−2i1−qq2i−qn+1=1−q1(i=1∑⌊2n⌋fim−2iq2i−qn+1i=1∑⌊2n⌋fim−2i)。
然后考虑分别算两个求和的值。第一个求和中,m−2iq2i=m−2i(mt+1)2i=m2ti,因此第一项可以变为 i=1∑⌊2n⌋fim2ti=S(⌊2n⌋,2t);第二个求和中 i=1∑⌊2n⌋fim−2i=S(⌊2n⌋,−2)。又因为 qn+1=m(n+1)(t+1),l=1∑nm(t+1)l=1−mt+1mt+1−m(n+1)(t+1),因此可得整个双重和式的值为 S(n,t)=1−mt+1mt+1−m(n+1)(t+1)−1−mt+1S(⌊2n⌋,2t)−m(n+1)(t+1)S(⌊2n⌋,−2),边界情况是 S(0,t)=0。
考虑分析一下时间复杂度。看上去很爆炸,但是考虑使用记忆化搜索来优化,设递归深度为 dep 则每次递归 n 的值都会除以 2,因此有 dep∼O(logn)。而在第 d 层可能出现的不同 t 值的状态也只有 O(d) 级别,因此总状态数就是 d=0∑O(logn)O(d)=O(log2n) 的。每次递归内层还需要做快速幂和记忆化(需要开 map 维护)等操作,因此总时间复杂度可以近似看作是 O(log3n) 级别的。
需要单独处理 mt+1≡1(mod109+7) 的情况,这个时候不能直接套上面的公式除过去,需要做一些特殊的处理。
递推过程中可能出问题的地方有下面三类:m1,1−m1,1−mt+11,考虑什么时候会出现问题:
第一类显然当且仅当 m≡0(mod109+7) 时会出问题,第二类显然当且仅当 m≡1(mod109+7) 时会出问题,第三类比较困难,可以想到如果 m≡1(mod109+7) 或 m≡−1(mod109+7) 时会出问题。下面可以证明会出问题的情况只有这两类,即证明除了 m≡±1(mod109+7) 不存在其余 m 满足 mt+1≡1(mod109+7)。考虑上述做法一开始递归的时候只会调用 S(L,1) 和 S(L,−2) 两个值,在递归的过程中会从 S(n,t) 开始不停的调用 S(⌊2n⌋,2t) 和 S(⌊2n⌋,−2) 两个函数,因此所有可能取到的 t 只有 t=2j(j∈N) 和 t=−2j(j∈N+) 两类情况。因为递归每次回将 n 除以 2,而题目中保证 n≤1018 因此这里可能出现的 j 不会超过单 log 级别也就是 60。
现在假设 m≡0(mod109+7),设 m 在模 109+7 意义下的乘法群中的阶是 d(也就是最小的满足 md≡1(mod109+7) 的正整数 d),根据费马小定理可知 d∣1000000006,而 1000000006 的质因数分解形式为 2×500000003,因此 d 的所有可能取值只有 1,2,500000003,1000000006 四种。
当 d=1 时显然只能取 m≡1(mod109+7),当 d=2 时有 m2≡1(mod109+7)∧m≡1(mod109+7),因此只能取 m≡−1(mod109+7)。
比较复杂的情况是 d 的后两个取值。如果有 mt+1≡1(mod109+7),则必有 d∣t+1。当 d 是 500000003 或者 1000000006 时都必然有 500000003∣t+1。考虑对 t 的值做分类讨论:
如果 t=2j,则必须满足 2j+1≡0(mod500000003) 即 2j≡−1(mod500000003);如果 t=−2j 则必须满足 1−2j≡0(mod500000003) 即 2j≡1(mod500000003)。因为 ord500000003(2)=500000002(500000002=2×412×148721 对三个质因子 q∈{2,41,148721} 验证都有 2500000002/q≡1(mod500000003)),因此此时必须满足 500000002∣j。而 2j≡−1(mod500000003) 必须满足 j≡250000001(mod500000002)。而因为在题目限制下递归满足 j≤60,因此不需要处理后两种情况。
现在考虑特判这三个 corner case:
- m≡0(mod109+7):此时答案显然为 0。
- m≡1(mod109+7):在模意义下只有一种字符,长度 k≥3 的唯一序列一定满足条件,取 l=1 即可。因此长度从 3 到 n 的序列都符合条件,答案即为 n−2。
- m≡−1(mod109+7):相对比较复杂的情况。设 F(x)=i=1∑xfi,当 m≡−1 时 fi=(−1)i(1−F(⌊2i⌋)),因此对 i≥1 有递推式 f2i=1−F(i),f2i+1=F(i)−1=−f2i。因此前缀和满足 F(2i+1)=−1,F(2i)=−F(i)。此时再定义 G(i)=j=1∑iF(j) 则因为奇数位置的 F 都为 −1,偶数位置的 F 都满足 F(2i)=−F(i),因此又有递推公式 G(x)=−⌈2x⌉−G(⌊2x⌋)。
- 然后考虑算答案。当 m≡−1 时固定 l 值后剩余部分的贡献为 k=3l∑n(−1)k−2l=k=3l∑n(−1)k,只有 l≡n(mod2) 时上式的值不为 0。此时考虑进一步分类讨论:
- 如果 2∣n 则只需要枚举偶数 l=2i,最终的答案可以表示为 ⌊2L⌋−G(⌊2L⌋) 的形式,其中 L=⌊3n⌋。
- 如果 2∤n 则只需要枚举奇数 l=2i+1,最终的答案可以表示为 ⌊2L+1⌋−G(⌊2L−1⌋) 的形式,其中 L=⌊3n⌋。
- 很显然 G(n) 可直接递归 O(logn) 时间复杂度求解,因此该部分算答案的总时间复杂度也是 O(logn) 的。
:::success[Code]
namespace lowspeed_song {
inline void init() {
}
int n, m;
map<pair<int, int>, int> mp;
inline int S(int n, int t) {
if (!n) return 0;
if (mp.count({n, t})) return mp[{n, t}];
int a = power(m, ((t + 1) % (mod - 1) + mod - 1) % (mod - 1), mod), b = power(m, ((i128)(n + 1) * (t + 1) % (mod - 1) + mod - 1) % (mod - 1), mod);
return mp[{n, t}] = inversion(mod + 1 - a) * (a - b - S(n / 2, t * 2) + mod * 2 + b * S(n / 2, -2) % mod) % mod;
}
inline int sumF(int n) {
if (!n) return 0;
return (mod + mod - sumF(n >> 1) - (n + 1) * i2 % mod) % mod;
}
inline int calc(int n) {
if (n & 1) return ((n / 3 + 1) / 2 % mod + mod - sumF(n / 2)) % mod;
return (n / 3 % mod + mod - sumF(n / 2)) % mod;
}
inline void sol() {
cin >> n >> m; m %= mod;
if (!m) cout << 0 << '\n';
else if (m == 1) cout << (n - 2) % mod << '\n';
else if (m == mod - 1) cout << calc(n) << '\n';
else {
mp.clear();
cout << inversion((1 - m + mod) % mod) * (S(n / 3, 1) - power(m, n + 1, mod) * S(n / 3, -2) % mod + mod) % mod << '\n';
}
}
} // namespace lowspeed_song
:::
喜欢的话,留下你的评论吧~