题解:CF2094H La Vaca Saturno Saturnita

· · 题解

VP 时想到的诡异非根号分治但通过做法。其实已经想了一个根号分治了,但感觉 Div.4 放根号分治有点遥遥领先了,所以没敢写。

题意简述:给定长度为 n 的正整数数列 a,模拟以下伪代码函数:

function f(k, a, l, r):
   ans := 0
   for i from l to r (inclusive):
      while k is divisible by a[i]:
         k := k/a[i]
      ans := ans + k
   return ans

函数调用 q 次,每次给出参数 k,l,r,回答结果。

--- 本题考查敢写。VP 时感觉此做法过于诡异,不敢写,费劲优化才逐渐信任此做法。 注意到 $k$ 一旦除以其他数,至少减半,所以实际次数为 $\log$ 量级。并且除以某个数后会直接把 $k$ 潜在的因子全部榨干(以后再遇到这个数就不会触发除了),实际上是不重复的,最坏是 $8! = 40320$,共 $7$ 次($1$ 不算在内)。 这意味着实际上很多的遍历到的 $a_i$ 并不对 $k$ 产生影响,考虑如何快速找到使得一个 $k$ 降低的位置,就可以计算一段区间的贡献($k$ 不变的一段),这个位置就是在 $l$ 后面最近的 $i$ 使得 $a_i | k$。这样子 $f(k,a,l,r)$ 就变成了 $k \times (i-l) + k_{new} + f(k_{new},a,i+1,r)$。 显然你不能开 $10^{10}$ 的空间,注意到 $10^5$ 以内的数因数个数肯定不会很多(实际最多为 $128$ 个),我们直接枚举其因数,然后用二分求最近的 $i$ 即可。 笔者 VP 时怕 TLE,改写成了一个奇奇怪怪的离线 + 队列,就没有二分的 $\log$ 了,交上去还不到时限的十分之一。 尽可能添加了自认为较为详细的注释,辅助代码理解可能会更好一些。~~(当然也可能需要更多的精力来理解我的丑陋代码)~~ 运算量为 $50000 \times 128 \times 7 = 44800000$,足以通过本题([389 ms 144900 KB](https://codeforces.com/contest/2094/submission/323763339))。本题甚至开了 4s 时限。 ```cpp #include<bits/stdc++.h> using namespace std; const int N=1e5; int t,n,q,a[100010]; long long ans[50050]; vector<int> f[100010];//f[i] i的因子 不包括 1 struct qu{ int id,k,r; }; queue<qu> b[100010];//离线查询 下标就是l queue<int> c[100010];//O(1)求最近i的队列 void init(){//预处理因子 for(int i=2;i<=N;i++){ for(int j=i;j<=N;j+=i)f[j].push_back(i); } } int main(){ cin.tie(0)->sync_with_stdio(0); cout.tie(0); cin>>t; init(); while(t--){ cin>>n>>q; for(int i=1;i<=n;i++){ cin>>a[i]; c[a[i]].push(i); } for(int i=1;i<=q;i++){ int k,l,r; cin>>k>>l>>r; b[l].push({i,k,r}); } for(int i=1;i<=n;i++){ while(!b[i].empty()){ qu _=b[i].front(); b[i].pop(); int new_l=_.r+1;//找最近的i,添加完贡献后就变成了另一个函数 //由于函数的l转化后单调递增 所以队列就以l为下标 循环里也是i从小到大 for(int j:f[_.k]){ while(!c[j].empty()&&c[j].front()<i)c[j].pop(); if(!c[j].empty())new_l=min(new_l,c[j].front()); } if(new_l>_.r)ans[_.id]+=1ll*(_.r-i+1)*_.k;//找不到的话就结束 else{ ans[_.id]+=1ll*(new_l-i)*_.k;//添加贡献 while(_.k%a[new_l]==0)_.k/=a[new_l]; ans[_.id]+=_.k; //添加贡献 if(new_l+1<=n)b[new_l+1].push(_); } } } for(int i=1;i<=q;i++)cout<<ans[i]<<'\n'; //下面都是多测清空 保险全清了 for(int i=1;i<=q;i++)ans[i]=0; for(int i=1;i<=n;i++){ while(!c[a[i]].empty())c[a[i]].pop(); while(!b[i].empty())b[i].pop(); a[i]=0; } } return 0; } // 50000 * 128 * 7 = 44800000 // https://codeforces.com/contest/2094/submission/323763339 ```