分块+二分
这道题思路很巧妙
我们大概可以推出一个式子sigma(d-[(ai-1)%d+1])<=k,要求求出d的最大值
然后我们化简一下,sigma(d-[(ai-1)-[(ai-1)/d]*d+1])<=k -> sigma(d-ai-[(ai-1)/d]*d)<=k
直接枚举肯定炸,但是我们看见里面有一个下取整除法,我们想到了什么?莫比乌斯反演中的分块技巧!那么我们可以通过分块来减少枚举d的复杂度,然后在一定取值范围内二分就行了!
然后,我们对于每个ai-1查找分块对应端点的最小值,也就是一段使得(ai-1)/d第一个变化的值,而其他值没有变化,也就是说我们对于每个ai枚举分块端点值后,每两个值相邻区间的值不会改变任何一个(ai-1)/d的值。
然后每个ai有sqrt(ai)个值,那么一共就有n*sqrt(max(ai))的值,然后我们从大到小枚举每个值,如果一个值满足条件,那么我们需要二分找出满足答案的最大值,因为这个值只是在从这个值到下一个值-1这一段区间内任意(ai-1)/d不变,但是不一定满足,由于现在(ai-1)/d不变,那么上面那个式子就满足单调性了,于是就可以二分了。
如果枚举的值范围过大,我们在看见除法的情况下可以用分块优化,可以大大降低复杂度,因为分块求出使一个值变化的最小的除数,这样我们就可以求出所有区间使得取这个区间内任意一个值所有数做除法的商不变
最后push_back(j)是(ai-1)/d==0,其实也就是ai
#include<bits/stdc++.h> using namespace std; const int N = 110; int n; long long k, ans, sum, m; long long a[N]; vector<long long> v; int main() { scanf("%d%I64d", &n, &k); for(int i = 1; i <= n; ++i) scanf("%I64d", &a[i]), sum += a[i], m = max(m, a[i]); long long tot = k + m; for(int i = 1; i <= n ; ++i) { long long j, t; for(j = 1, t = 0; j < a[i] && t < a[i]; j = t + 1) v.push_back(j), t = (a[i] - 1) / ((a[i] - 1) / j); v.push_back(j); } for(int i = 0; i < v.size(); ++i) printf("%I64d ", v[i]); puts(""); sort(v.begin(), v.end()); v.erase(unique(v.begin(), v.end()), v.end()); v.push_back(100000000000000ll); for(int i = v.size() - 1; i >= 0; --i) { long long x = v[i]; long long tot = 0; for(int j = 1; j <= n; ++j) tot += (a[j] - 1) / x; if(x * tot <= k + sum - (long long)n * x) { long long l = x - 1, r = v[i + 1]; while(r - l > 1ll) { long long mid = (l + r) >> 1ll; if(mid * tot <= k + sum - (long long)n * mid) l = ans = mid; else r = mid; } break; } } cout << ans << endl; return 0; }