要理解这段代码,需结合题目要求和题解的逆变换 + 辗转相除思路,逐部分分析:
-
题目核心与题解思路
回顾题目要求:给定两个线性变换 \(T1((x,y))=(x+y,y)\)、\(T2((x,y))=(x,x+y)\),对于每个查询向量 \((x,y)\),统计有多少个给定的向量可通过若干次\(T1/T2\)变换(包括 0 次)得到。
题解思路:正向变换可能无限多,逆变换分析更高效。
观察到:若某一步向量的分量 \(x \leq 0\) 或 \(y \leq 0\),后续会一直保持,而查询的 \((x,y)\) 满足 \(x,y>0\),因此逆变换只有 “大数减小数的倍数” 这一种操作,类似欧几里得辗转相除。对每个给定向量,预处理其 “逆变换的所有中间状态”,存储后排序;查询时,对\((x,y)\)做同样的逆变换,通过二分查找统计符合条件的中间状态数量。 -
代码结构与关键逻辑解释
(1)数据结构与输入处理
tri 是 std::tuple<int,int,int>,用于存储逆变换过程中的三元组(记录当前分量、余数、原分量信息)。p[0] 和 p[1] 是两个数组,分别存储 “\(a \leq b\)” 和 “\(a > b\)” 两种情况下的三元组;m[0]、m[1] 是对应数组的元素计数。处理每个给定向量 \((a,b)\) 的循环:for(int a,b,f;n--;){a=read(),b=read(),f=0;while(a&&b)if(a<=b)p[0][++m[0]]={a,b%a,b-f},b%=a,f=1;else p[1][++m[1]]={b,a%b,a-f},a%=b,f=1; }
这里模拟逆变换的辗转相除过程:若 \(a \leq b\),逆变换对应 “由 \((b, a)\) 变换到 \((a, b\%a)\)”,记录三元组 \((a, b\%a, b - f)\)(b - f 用于后续查询时判断原分量范围),然后更新 \(b = b\%a\),标记 \(f=1\)(表示已进行一次操作)。若 \(a > b\),类似处理,记录三元组 \((b, a\%b, a - f)\),更新 \(a = a\%b\),标记 \(f=1\)。循环直到 a 或 b 为 0,此时所有中间状态都被记录到 p[0] 或 p[1] 中。
(2)排序预处理
for(int o=2;o--;)std::sort(p[o]+1,p[o]+1+m[o]);
对 p[0] 和 p[1] 分别排序,为后续二分查找做准备,确保能快速统计符合条件的三元组数量。
(3)查询处理
for(int a,b,ans;q--;){a=read(),b=read(),ans=0;ans+=std::upper_bound(p[0]+1,p[0]+1+m[0],tri{a,b%a,(int)1e18})-p[0];ans-=std::lower_bound(p[0]+1,p[0]+1+m[0],tri{a,b%a,b})-p[0];ans+=std::upper_bound(p[1]+1,p[1]+1+m[1],tri{b,a%b,(int)1e18})-p[1];ans-=std::lower_bound(p[1]+1,p[1]+1+m[1],tri{b,a%b,a})-p[1];printf("%lld\n",ans); }
对每个查询 \((x,y)\)(代码中用a,b接收),模拟逆变换的辗转相除步骤,分两种情况统计:
-
情况 1(对应p[0]):
计算 \(y\%x\),在 p[0] 中查找三元组满足 “第一个元素是x、第二个元素是 \(y\%x\)、第三个元素在 \([y, 1e18)\) 范围内” 的数量(通过 upper_bound 和 lower_bound 的差实现)。 -
情况 2(对应p[1]):
计算 \(x\%y\),在 p[1] 中查找三元组满足 “第一个元素是y、第二个元素是\(x\%y\)、第三个元素在\([x, 1e18)\)范围内” 的数量。
两者数量之和即为该查询的答案。
-
-
时间复杂度与效率预处理阶段:
每个向量的辗转相除次数是 \(O(\log V)\)(V 是分量最大值),共 n 个向量,且排序的时间是 \(O(n \log V \log n)\)。
查询阶段:每个查询的辗转相除次数是 \(O(\log V)\),每次二分查找是 \(O(\log n)\),共 q 个查询,总时间是 \(O(q \log V \log n)\)。
整体复杂度 \(O((n + q) \log V \log n)\),能高效处理题目中的大规模数据。
综上,这段代码通过逆变换 + 辗转相除 + 二分查找的思路,将原问题转化为对 “变换中间状态” 的统计问题,实现了高效的查询。