10.21 so

题单介绍

考虑根号分治(不过我是被人提醒才想到的,我不知道为什么会想到根号分治awa。 先对 $k_i$ 根号分治。 当 $k_i \geq A$ 时,至多有 $\dfrac{n}{A}$ 个位置的函数值小于等于 $n$,直接暴力考虑它在这些位置的函数值是否是对应 $a_i$ 的倍数,这样考虑一个函数的时间复杂度是 $O(\dfrac{n}{A})$,而至多有 $O(n)$ 个函数的 $k_i$ 大于等于 $A$,所以这部分总的时间复杂度是 $O(\dfrac{n^2}{A})$。 当 $k_i \lt A$ 时,至多有 $A$ 种本质不同的 $k_i$,那么对这些函数按 $k_i$ 分类,每一类中有许多 $b_j$。 此时再对 $a_i$ 根号分治。 当 $a_i \geq B$ 时,其至多有 $\dfrac{n}{B}$ 个小于等于 $n$ 的倍数,不妨考虑它的这些倍数,是否被某个一次函数经过了,发现对于同类的一次函数,它们均平行,只有满足等于 $b_j = ga_i-ik_i$ 的该类一次函数才会经过 $(i,ga_i)$,其中 $ga_i$ 表示 $a_i$ 的某个倍数。 所以相当于查询每一类 $k_i$ 中的 $b_j = a_i-ik_i$ 的个数,可以用的用 ```unordered_map``` 记一下,$O(1)$ 查询。 至多有 $n$ 个 $a_i \geq B$,对于每个 $a_i$,至多有 $\dfrac{n}{B}$ 个小于等于 $n$ 的倍数,然后对于每个倍数,要考虑 $A$ 种一次函数中经过 $(i,ga_i)$ 的个数,这样这部分总的时间复杂度就是 $O(\dfrac{n^2A}{B})$。 当 $a_i \lt B$ 时,至多有 $B$ 种本质不同的 $a_i$。 对于一个 $a_i$,考虑每类一次函数,当 $b_i \equiv -ik \pmod a_i$ 时该函数对 $a_i$ 有贡献,所以记录每类函数的 $b_i$ 模 $B$ 种不同的 $a_i$ 的余数的出现次数。 这样被预处理的函数总数是 $O(n)$ 的,并统计了它们的 ¥$b_j$ 对 $O(B)$ 种不同的模数取模时的余数出现个数,这部分的时间复杂度是 $O(nB)$。 查询最多考虑 $O(n)$ 个 $a_i$,每次考虑 $O(A)$ 类函数的贡献,这部分的时间复杂度是 $O(nA)$。 所以最后的时间复杂度是 $O(\dfrac{n^2}{A}+\dfrac{n^2A}{B}+nB+nA)$。 考虑对 $\dfrac{n^2A}{B}+nB$ 使用均值不等式,有 $\dfrac{n^2A}{B}+nB \geq 2\sqrt{n^3A} = 2n^{\frac{3}{2}}A^{\frac{1}{2}}$,当且仅当 $B = \sqrt {nA}$ 时等号成立。 此时要调整 $A$ 使 $2n^{\frac{3}{2}}A^{\frac{1}{2}}+nA+n^2A^{-1}$ 中最后各项的 $n^k$ 中 $k$ 的最大值最小,不妨设 $A = n^x$ 次方,则三项为 $n^{\frac{1}{2}x+\frac{3}{2}}+n^{x+1}=n^{-x+2}$,考虑画出三个关于 $x$ 的一次函数图像,求三者较大值的最小值,容易得出 $x = \dfrac{1}{3}$ 时,时间复杂度最好,为 $O(n^{\frac{5}{3}})$,不知道能不能通过 $n=10^5$() 此时 $A = n^{\frac{1}{3}},B = n^{\frac{2}{3}}$。 # 一次函数计数问题的根号分治思路(通俗版) 先帮你把问题和思路拆解开,用更直白的语言讲清楚,再梳理复杂度逻辑,最后判断可行性。 ## 一、先搞懂要解决什么问题 给定两个核心输入: 1. **n个一次函数**:每个函数形式为 $f_j(x) = k_j \cdot x + b_j$(其中 $j \in [1,n]$,$k_j$、$b_j$ 是给定参数); 2. **长度为n的序列a**:每个位置 $i \in [1,n]$ 对应一个值 $a_i$。 最终要求:对每个 $i$,统计满足以下两个条件的函数 $f_j$ 的个数: - 条件1:函数在 $x=i$ 处的结果 $f_j(i) \leq n$; - 条件2:结果 $f_j(i)$ 能被 $a_i$ 整除(即 $a_i \mid f_j(i)$)。 ## 二、核心思路:用“根号分治”拆分难题 根号分治的本质是“按参数规模拆分问题,对不同规模用不同高效方法”,这里会拆两次:先按函数的 $k_j$ 大小拆,再按序列的 $a_i$ 大小拆,避免暴力枚举的 $O(n^2)$ 复杂度。 ## 三、分情况处理:把复杂问题拆成3个小任务 先定义两个“阈值” $A$ 和 $B$(后续会讲如何选择最优值),然后分3种情况计算每个 $i$ 的答案。 ### 情况1:处理 $k_j \geq A$ 的函数(k值大的函数) $k_j$ 越大,$f_j(i) = k_j \cdot i + b_j$ 越容易超过 $n$(因为 $i$、$b_j$ 均为正整数)。因此: - 满足“$f_j(i) \leq n$”的 $i$ 很少:最多只有 $\frac{n}{A}$ 个(比如 $k_j \geq A$ 时,$i$ 最大只能是 $\frac{n}{A}$,否则 $k_j \cdot i$ 会直接超过 $n$); - 处理方式:暴力枚举每个 $k_j \geq A$ 的函数,再枚举它能覆盖的 $i$,检查“$f_j(i)$ 是否能被 $a_i$ 整除”,直接计数; - 时间复杂度:最多 $n$ 个函数,每个函数查 $\frac{n}{A}$ 个 $i$,总耗时 $O\left(\frac{n^2}{A}\right)$。 ### 情况2:处理 $k_j < A$ 且 $a_i \geq B$ 的情况(k小但a大) $k_j < A$ 时,函数“增长平缓”($i$ 增大时 $f_j(i)$ 涨得慢);但 $a_i \geq B$ 时,$a_i$ 的“小于等于n的倍数”很少(最多 $\frac{n}{B}$ 个,比如 $a_i=B$ 时,倍数是 $B、2B、…、\lfloor \frac{n}{B} \rfloor \cdot B$)。此时换角度计算: 1. 先给 $k_j$ 分类:因 $k_j < A$,最多只有 $A$ 种不同的 $k$(比如 $k=1、2、…、A-1$),把相同 $k$ 的函数归为一类; 2. 用哈希表记录每类的 $b_j$ 次数:比如对 $k=2$ 的类,用 ``unordered_map`` 记录“$b_j=3$ 出现5次”“$b_j=5$ 出现3次”等; 3. 对每个 $a_i \geq B$ 的 $i$: - 先找 $a_i$ 所有 $\leq n$ 的倍数 $g$(即 $g = t \cdot a_i$,其中 $t$ 是正整数且 $g \leq n$); - 对每个倍数 $g$,要找“满足 $f_j(i)=g$”的函数:代入函数式得 $b_j = g - k_j \cdot i$; - 对每类 $k_j$(共 $A$ 类),查哈希表中“$b_j = g - k_j \cdot i$”的次数,累加就是这类函数的贡献; - 时间复杂度:$n$ 个 $a_i$,每个查 $\frac{n}{B}$ 个倍数,每个倍数查 $A$ 类 $k$,总耗时 $O\left(\frac{n^2 A}{B}\right)$。 ### 情况3:处理 $k_j < A$ 且 $a_i < B$ 的情况(k小且a小) $a_i < B$ 时,$a_i$ 的种类很少(最多 $B$ 种,比如 $a=1、2、…、B-1$),可以提前“预处理余数”加速: 1. 预处理阶段(提前算好备用): - 对每个 $k_j < A$ 的函数(共 $n$ 个),对所有 $a \in [1,B-1]$,计算“$b_j$ 除以 $a$ 的余数”; - 用数组记录计数:比如“$k=2$ 类、$a=3$、余数=1”的计数器加1,代表该类中 $b_j \equiv 1 \pmod{3}$ 的函数有1个; 2. 查询阶段(计算每个 $i$ 的答案): - 对每个 $a_i < B$ 的 $i$,要满足“$f_j(i)$ 能被 $a_i$ 整除”:代入函数式得 $k_j \cdot i + b_j \equiv 0 \pmod{a_i}$,即 $b_j \equiv -k_j \cdot i \pmod{a_i}$; - 对每类 $k_j$(共 $A$ 类),直接查“$k_j$ 类、$a=a_i$、余数=$(-k_j \cdot i) \mod a_i$”的计数器值,累加就是贡献; - 时间复杂度:预处理是 $O(nB)$($n$ 个函数,每个算 $B$ 个余数),查询是 $O(nA)$($n$ 个 $i$,每个查 $A$ 类 $k$),总耗时 $O(nB + nA)$。 ## 四、复杂度优化:如何选A和B最划算? 将3种情况的复杂度相加,总时间复杂度为: $$O\left(\frac{n^2}{A} + \frac{n^2 A}{B} + nB + nA\right)$$ 核心目标是选 $A$ 和 $B$,让总耗时最小,关键是让各项“量级尽可能接近”(根号分治的核心思想): 1. 先定 $A$:设 $A = n^x$($x$ 是常数),代入后分析可知,当 $x = \frac{1}{3}$(即 $A$ 是 $n$ 的立方根)时,各项的“$n$ 的指数”最小; 2. 再定 $B$:根据 $A = n^{1/3}$,用均值不等式可得 $B = n^{2/3}$(即 $n$ 的三分之二次方)。 最终最优复杂度为 $O(n^{5/3})$——对 $n=1e5$ 来说,实际运算次数约 $2 \times 10^8$ 次(此前计算过),而C++每秒可处理 $10^8 \sim 10^9$ 次基本运算,3秒时限完全足够。 ## 结论 该思路通过“拆k、拆a”两次根号分治,将原本可能 $O(n^2)$ 的难题降到 $O(n^{5/3})$。对 $n=1e5$ 的场景,C++代码只需简单优化(如用数组替代部分哈希表、减少冗余计算),3秒内可稳定通过。 ```cpp #include <cstdio> #include <algorithm> #include <vector> #include <ctime> #include <unordered_map> #include <cmath> #include <vector> static char stkk[200]; #define ve std::vector #define up std::unordered_map #define pb push_back #define FOR(i,a,b) for(int i = (a);i <= (b);++i) #define REP(i,a,b) for(int i = (a);i >= (b);--i) typedef long long ll; inline void output(int x){ if(!x)return putchar('0'),void(); if(x<0)x = ~x+1,putchar('-'); int top = 0; for(;x;stkk[++top]=x%10^48,x/=10); for(;top;putchar(stkk[top--])); } inline void readx(int &x){ x = 0;int y = 1;char c = getchar(); for(;c<48||c>58;c = getchar())if(c=='-')y = -1; for(;c>=48&&c<=58;c = getchar())x = (x<<1)+(x<<3)+(c^48); x *= y; } const int N = 1e5+10,AA = 50,BB = 2200; static int T,n,V,tid,A,B; static int k[N],b[N],a[N],f[N]; static int pk[N],pktp,pa[N],patp,ct[AA][N],tct[AA][BB]; static int xx[N],xxtp; static ve<int> bb[AA]; static int qbb[AA]; signed main(){ freopen("summer.in","r",stdin); freopen("summer.out","w",stdout); // output(std::pow(N,2.0/3)); readx(tid); readx(T),readx(n),readx(V); FOR(i,1,n)readx(k[i]),readx(b[i]); FOR(i,1,T)readx(a[i]); A = std::pow(n,1.0/3),B = std::pow(n,2.0/3); FOR(i,1,n){ if(k[i]>=A){ int v = b[i]; FOR(j,1,T){ if((v+=k[i])>V)break; !(v%a[j])&&(++f[j]); } } else xx[++xxtp] = k[pk[++pktp]=i]; } std::sort(xx+1,xx+1+xxtp);xxtp = std::unique(xx+1,xx+1+xxtp)-xx-1; FOR(kid,1,pktp){ int id = pk[kid]; ++ct[k[id]=std::lower_bound(xx+1,xx+1+xxtp,k[id])-xx][b[id]],bb[k[id]].pb(b[id]); } FOR(i,1,xxtp)std::sort(bb[i].begin(),bb[i].end()); FOR(i,1,T){ if(a[i]>=B){ for(ll v = a[i],tmp;v<=V;v+=a[i]){ FOR(t,1,xxtp){ if((tmp=1ll*i*xx[t])>v)break; f[i]+=ct[t][v-tmp]; } } } else pa[++patp] = i; } std::sort(pa+1,pa+1+patp,[&](int x,int y){return a[x]^a[y]?a[x]<a[y]:x<y;}); a[0] = -1; for(int i = 2,j = 1,v;i <= patp+1;++i){ if(a[pa[i]]!=a[pa[j]]){ v = a[pa[j]]; FOR(tk,1,xxtp){ qbb[tk] = bb[tk].size()-1; FOR(tv,0,v)tct[tk][tv] = 0; } FOR(kid,1,pktp){ int id = pk[kid]; ++tct[k[id]][b[id]%v]; } FOR(aid,j,i-1){ int id = pa[aid]; FOR(tk,1,xxtp){ for(;~qbb[tk]&&1ll*xx[tk]*id+bb[tk][qbb[tk]]>V;--tct[tk][bb[tk][qbb[tk]--]%v]); } FOR(tk,1,xxtp)f[id]+=tct[tk][(v-1ll*xx[tk]*id%v)%v]; } j = i; } } FOR(i,1,T)output(f[i]),putchar(10); return 0; } ```

题目列表