(本文是基于队爷 @s_r_f 所写题解的完善和补充)
可以发现,如果我们暴力地枚举一个数的因数或倍数,时间复杂度是可接受的。
对于前者,单次复杂度为 O(n)O(\sqrt n)O(n);对于后者,由于题目保证了 C≤10C \leq 10C≤10,所以单次的均摊复杂度应为调和级数 O(lnn)O(\ln n)O(lnn)。
所以,我们可以考虑直接模拟题意中的操作。
首先,我们设 fif_ifi 为以 iii 开头的最长上升倍数子序列的长度。同时,为了方便统计答案的第二项,我们再设一个 fcntifcnt_ifcnti,表示有多少个 jjj 满足 fj=if_j = ifj=i。
有了以上两个数组,我们就可以从小到大枚举 iii,当 fcntifcnt_ifcnti 不为 000 时,我们就同时找到了答案的第一项和第二项,一举两得。
没错!这样做的理论复杂度是 O(nq)O(nq)O(nq) 的,会超时。但这真的是实际的复杂度吗?
注意题目中有这样一句话:保证在任意时刻序列 AAA 非空,其中的元素互不相等。
也就是说,就算是最坏的情况,这个子序列的长度也是不超过 O(log2m)O(\log_2 m)O(log2m) 的,所以上述做法的实际复杂度为:O(qlog2m)O(q\log_2m)O(qlog2m)。
解决完了询问,我们再看看四个操作中哪个最好想。
显然是在序列左端进行的两个操作。究其原因,我们先来推推 fif_ifi 的转移方程式:
考虑枚举 iii 的倍数 jjj,如果 jjj 存在,设 jjj 的位置为 posjpos_jposj,则有:
fi=maxi∣j,posj>0{fj+1}
f_i = \max_{i | j, pos_j > 0}\{f_j + 1\}
fi=i∣j,posj>0max{fj+1}
所以,在左端插入一个数,我们只需要枚举它的倍数转移即可。前面提到了,单次均摊复杂度为 O(lnn)O(\ln n)O(lnn)。
而在左端删除一个数则更加简单,因为最左端的数不会对右侧的任何位置造成任何贡献,所以让它平静地离队即可。单次复杂度 O(1)O(1)O(1)。
我们再来考虑序列右端的两个操作。实际上,它们应当是大同小异的,都是枚举因数计算贡献,如果插入则把贡献也加入进去,否则把贡献删除。
现在我们需要完成的操作是:加入或者删除一个位置的贡献。
先放上代码:(valvalval 表示需要加入或者删除贡献的数,tmpftmpftmpf 表示的是需要加入或删除的贡献)
inline void Modify(int val, int tmpf)
{
++fcnt[tmpf];
for(register int i = 1; i <= Sqrt[val]; i++) if(val % i == 0)
{
if(pos[i] && pos[i] < pos[val]) ++g[pos[i]][tmpf + 1];
if(i * i != val && pos[val / i] && pos[val / i] < pos[val]) ++g[pos[val / i]][tmpf + 1];
}
return;
}
inline void Delete(int val, int tmpf)
{
--fcnt[tmpf];
for(register int i = 1; i <= Sqrt[val]; i++) if(val % i == 0)
{
if(pos[i] && pos[i] < pos[val]) --g[pos[i]][tmpf + 1];
if(i * i != val && pos[val / i] && pos[val / i] < pos[val]) --g[pos[val / i]][tmpf + 1];
}
return;
}
我们发现,为了方便维护,代码中加设了 gi,leng_{i, len}gi,len 表示以 iii 开头,长度为 jjj 的最长上升倍数子序列有多个。(这个在前面左端插入的操作中也需要维护)
这个数组的作用类似 fcntfcntfcnt,是为了方便我们在修改贡献后,找到 fff 数组最终被修改成了什么样子。
有了这两个函数,我们就可以先把 valvalval 的贡献加入,然后枚举所有 valvalval 的因数,把所有修改过的位置更新即可。
具体实现的时候,对于没有修改过的位置可以选择不修改,可以节省一点常数。
右侧的删除与加入几乎一样,不过应当先删去而非加上 valvalval 的贡献。
代码:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#define MAXN 300005
#define MAXM 1000001
#define MAXLOG 21
using namespace std;
int n, m, q, a[MAXN], pos[MAXM], f[MAXN], oldf[MAXN], fcnt[MAXLOG], g[MAXN][MAXLOG], Sqrt[MAXM], len, seq[MAXM];
namespace Deque
{
int l, r, a[MAXN];
inline void Prework() { return r = n + q, l = n + q + 1, void(); }
inline void Modify(int val, int tmpf) //加入贡献
{
++fcnt[tmpf];
for(register int i = 1; i <= Sqrt[val]; i++) if(val % i == 0)
{
if(pos[i] && pos[i] < pos[val]) ++g[pos[i]][tmpf + 1];
if(i * i != val && pos[val / i] && pos[val / i] < pos[val]) ++g[pos[val / i]][tmpf + 1];
}
return;
}
inline void Delete(int val, int tmpf) //删除贡献
{
--fcnt[tmpf];
for(register int i = 1; i <= Sqrt[val]; i++) if(val % i == 0)
{
if(pos[i] && pos[i] < pos[val]) --g[pos[i]][tmpf + 1];
if(i * i != val && pos[val / i] && pos[val / i] < pos[val]) --g[pos[val / i]][tmpf + 1];
}
return;
}
inline void Push_Front(int val)
{
a[--l] = val, pos[val] = l;
memset(g[l], 0, sizeof g[l]);
f[l] = g[l][1] = 1;
for(register int i = val * 2; i < MAXM; i += val) if(pos[i]) ++g[l][f[pos[i]] + 1];
for(register int i = MAXLOG - 1; i >= 0; i--) if(g[l][i]) { ++fcnt[f[l] = i]; break; }
return;
}
inline void Push_Back(int val)
{
a[++r] = val, pos[val] = r;
memset(g[r], 0, sizeof g[r]);
f[r] = g[r][1] = 1;
Modify(val, f[r]);
len = 0;
for(register int i = 1; i <= Sqrt[val]; i++) if(val % i == 0) seq[++len] = i, oldf[pos[i]] = f[pos[i]];
for(register int i = Sqrt[val]; i >= 1; i--) if(i * i != val && val % i == 0) seq[++len] = val / i, oldf[pos[val / i]] = f[pos[val / i]];
int id;
for(register int i = len; i >= 1; i--) if(id = pos[seq[i]])
{
for(register int j = MAXLOG - 1; j >= 1; j--) if(g[id][j]) { f[id] = j; break; }
if(f[id] != oldf[id]) Delete(seq[i], oldf[id]), Modify(seq[i], f[id]);
}
return;
}
inline void Pop_Front()
{
--fcnt[f[l]], f[l] = 0, pos[a[l++]] = 0;
return;
}
inline void Pop_Back()
{
int val = a[r];
Delete(val, f[r]);
len = 0;
for(register int i = 1; i <= Sqrt[val]; i++) if(val % i == 0) seq[++len] = i, oldf[pos[i]] = f[pos[i]];
for(register int i = Sqrt[val]; i >= 1; i--) if(i * i != val && val % i == 0) seq[++len] = val / i, oldf[pos[val / i]] = f[pos[val / i]];
int id;
for(register int i = len; i >= 1; i--) if(id = pos[seq[i]])
{
for(register int j = MAXLOG - 1; j >= 1; j--) if(g[id][j]) { f[id] = j; break; }
if(f[id] != oldf[id]) Delete(seq[i], oldf[id]), Modify(seq[i], f[id]);
}
f[r] = 0, pos[val] = 0, --r;
return;
}
inline void Solve() //求解答案
{
int ans = 0;
for(register int i = MAXLOG - 1; i >= 1; i--) if(fcnt[i]) { ans = i; break; }
printf("%d %d\n", ans, fcnt[ans]);
return;
}
}
namespace dq = Deque;
int main()
{
#ifdef FILE
freopen("Input.in", "r", stdin);
#endif
scanf("%d%d%d", &n, &m, &q);
dq :: Prework();
for(register int i = 1; i <= n; i++) scanf("%d", a + i);
for(register int i = n; i >= 1; i--) dq :: Push_Front(a[i]); //当然也可以用 Push_Back,不过这里会省下一些常数
for(register int i = 1; i <= m; i++) { Sqrt[i] = min(i, (int)sqrt(i) + 1); if(Sqrt[i] * Sqrt[i] > i) --Sqrt[i]; }
dq :: Solve();
while(q--)
{
int opt; scanf("%d", &opt);
if(opt == 0) { int val; scanf("%d", &val); dq :: Push_Front(val); }
if(opt == 1) { int val; scanf("%d", &val); dq :: Push_Back(val);}
if(opt == 2) dq :: Pop_Front();
if(opt == 3) dq :: Pop_Back();
dq :: Solve();
}
return 0;
}
773



被折叠的 条评论
为什么被折叠?



