3 条题解

  • 1
    @ 2026-8-19 16:34:03

    #include<bits/stdc++.h>
    using namespace std;
     
    typedef long long LL;
    const int N = 1e5 + 10;
    const LL P = 1e8;
     
    LL a[N];
     
    int main () {
    	ios::sync_with_stdio(false);
    	cin.tie(0);
    	
    	LL n, K;
    	cin >> n >> K;
    	for (int i = 1; i <= n; i ++) {
    		cin >> a[i];
    	}
    	LL ans = 0;
    	for (int j = 1; j <= n; j ++) {
    		LL sum = 0;
    		LL t = 1;
    		while (t <= K && t <= a[j]) {
    			LL ii = a[j] / t;
    			LL si = min(K, a[j] / ii);
    			sum += (si - t + 1) * min(ii * (a[j] + 2), P);
    			t = si + 1;
    		}
    		ans += sum;
    	}
    	cout << ans << "\n";
    	
    	return 0;
    } 
    
    
    • 0
      @ 2026-8-12 0:08:53

      题意

      给定正整数 n,kn,k 和长为 nn 的序列 aa,求:

      $$\sum_{p=1}^k\sum_{i=1}^n\min(10^8,\lfloor \frac{a_i}{p}\rfloor\times (a_i+2))$$

      思路

      考虑到如何把第一个和式简化掉,我们可以试着列出所有的 ni\lfloor \frac{n}{i}\rfloor,其中 i[1,n]i\in[1,n]。这里我们设 n=30n=30,那么可以列出这么一张表:

      1 30
      2 15
      3 10
      4 7
      5 6
      6 5
      7 4
      8 3
      9 3
      10 3
      11 2
      12 2
      13 2
      14 2
      15 2
      16-30 1
      

      不难发现,有几段他们的商是一样的,而且我可以确切的告诉你,块的数量是 n\sqrt n 级别的,这便是我们今天的主角——整除分块。

      整除分块

      虽说名字里带分块,但是与正宗的数列分块还是值域分块相比,都要简单很多。跟上面的例子一样,我们把商相同的除数归为一块,可以将时间复杂度从 O(n)O(n) 降至 O(n)O(\sqrt n)

      块的大小

      对于 ini\le \sqrt n:此时块的个数最多是 n\sqrt n,即每个 ii 单独一块。

      对于 i>ni> \sqrt n:此时块的个数最多也是 n\sqrt n,因为 ni\lfloor \frac{n}{i}\rfloor 肯定是小于等于 n\sqrt n 的。根据定义,所以块的个数也是 n\sqrt n 级别的。

      块的端点

      对于一个块,如果他的左端点为 ll,那么块的值 x=nlx=\lfloor \frac{n}{l}\rfloor。那么根据定义,块的右端点 rr 满足 r×xnr\times x\le n,所以 $r=\lfloor \frac{n}{x}\rfloor=\lfloor \frac{n}{\lfloor \frac{n}{l}\rfloor}\rfloor$。

      所以这题就是整除分块的模板了吧。

      代码

      #include<bits/stdc++.h>
      #define int long long
      using namespace std;
      int n,k,x,sum;
      signed main(){
          cin>>n>>k;
          while(n--){
              cin>>x;
              for(int l=1;l<=min(x,k);){
                  int r=min(x/(x/l),min(x,k));
                  sum+=(r-l+1)*min(100000000ll,(x/l)*(x+2));
                  l=r+1;
              }
          }
          cout<<sum;
          return 0;
      }
      

      结语

      总结一下,整除分块并没有我们想象的那么难,结论非常好推,而且不会推肯定也能记住,算是数论里比较简单了的了。

      另外本题还存在时间复杂度更优的树状数组解法,留给读者思考。

      • 0
        @ 2026-8-12 0:07:12

        如果题解有问题,请私信我,我会在讨论区更正。

        本题前置知识点:整除分块

        如果你已经会了整除分块,请跳过下面的内容。


        整除分块

        引一个例子来理解:

        如下有一道例题,请你用编程实现:

        给你一个整数 nn,请你输出一个长度为 nn 的数列 a1,a2,,ana_1,a_2,\dots,a_naia_i 表示对于 11nn 中每一个数,哪些数 nn 向下取整的结果等于 ii

        如果在 1n1091 \le n \le 10^9 的情况下,那么一个一个数遍历的思路就不可实现,这时候,我们可以用整除分块。

        1010 举例,我们来观察一下一般怎么实现:

        • 10÷1=10\lfloor 10 \div 1 \rfloor = 10a10a_{10} 的值加一;
        • 10÷2=5\lfloor 10 \div 2 \rfloor = 5a5a_{5} 的值加一;
        • 10÷3=3\lfloor 10 \div 3 \rfloor = 3a3a_{3} 的值加一;
        • 10÷4=2\lfloor 10 \div 4 \rfloor = 2a2a_{2} 的值加一;
        • 10÷5=2\lfloor 10 \div 5 \rfloor = 2a2a_{2} 的值加一;
        • 10÷6=1\lfloor 10 \div 6 \rfloor = 1a1a_{1} 的值加一;
        • 10÷7=1\lfloor 10 \div 7 \rfloor = 1a1a_{1} 的值加一;
        • 10÷8=1\lfloor 10 \div 8 \rfloor = 1a1a_{1} 的值加一;
        • 10÷9=1\lfloor 10 \div 9 \rfloor = 1a1a_{1} 的值加一;
        • 10÷10=1\lfloor 10 \div 10 \rfloor = 1a1a_{1} 的值加一。

        发现了吗,在后大半段时结果大都为 11,程序会将这些冗余的情况全部遍历一遍,所以比如 ii 第一次使商为某个数时我们可以计算 n÷n÷i\lfloor n \div \lfloor n \div i \rfloor \rfloor 什么时候结果最后一次为 n÷i\lfloor n \div i \rfloor,计算出来后将下表快速移动就可以降低时间复杂度。


        接下来我们来看怎么做这道题。

        这道题让我们求:

        $$\sum_{i=1}^{n} \sum_{j=1} ^ {k} \min(\lfloor a_i \div j\rfloor \times (a_i + 2) ,10^8)$$

        那么我们最外面的求和符号(即 i=1n\sum_{i=1}^{n})可以用一层 for 循环实现,对于里面一层,不就和我们刚才说的例题完全一致吗?

        时间复杂度 O(nk)O(nk)O(nk) \rightarrow O(n\sqrt{k}),可以通过。

        ::::success[AC代码]

        #include<bits/stdc++.h>
        #define int long long
        using namespace std;
        const int N = 1e8;
        int a[100001];
        signed main() {
        	int n, k, ans = 0;
        	cin >> n >> k;
        	for(int i = 1; i <= n; i++) {
        		cin >> a[i];
        	}
        	for(int i = 1; i <= n; i++) {
        		int x = a[i] + 2;
        		int t = 1;
        		while(t <= k && t <= a[i]) {
        			int l = a[i] / t;
        			int r = min(k, a[i] / l);
        			ans += min(N, l * x) * (r - t + 1);
        			t = r + 1;
        		}
        	}
        	cout << ans;
        	return 0;
        }
        

        ::::

        • 1

        信息

        ID
        12635
        时间
        2000ms
        内存
        512MiB
        难度
        8
        标签
        递交数
        94
        已通过
        17
        上传者