暴力:
for(int i=1;i<=n;i++)
ans+=n/i;//有手就行
这个的时间复杂度是 n 2 n^2 n2
正解:
i / j i/j i/j最终的答案一定是单调递减的而且可能还有一段是连续的一段数字.
for(int i=1,r;i<=n;i=r+1){
r=n/(n/i);
ans+=(r-i+1)*(n/i);
}
这个的时间复杂度是 n \sqrt{n} n
字很丑,格式也没有手打好看,不过只要能看就行啦,反正是学知识(强词夺理)
可以发现我们只是把 i i i变成了 a i + b ai+b ai+b,所以我们按照上面的推导方式再推导一次就行.
答案也容易求,就是和上面的一个步骤,求 r r r,用区间个数乘上 k k k就完事儿了.
和上道题差不多,可以用来巩固一下,或者找找解题的感觉.
先把模运算换做除运算 n % i = n − ⌊ n i ⌋ ∗ i n\%i=n-\lfloor \frac{n}i{} \rfloor * i n%i=n−⌊in⌋∗i,然后就能出现我们熟悉的式子了,熟悉,但不完全熟悉,假设一个区间 l , r l,r l,r,在这个区间 ⌊ n i ⌋ \lfloor \frac{n}i{} \rfloor ⌊in⌋的值都一样, i i i变大 1 1 1,那么整个式子就变小了 ⌊ n i ⌋ \lfloor \frac{n}i{} \rfloor ⌊in⌋,这个值是一个常数,所以就是一个等差数列,就可以 O 1 O1 O1的计算出来.
答案会溢出,所以对 998244353 998244353 998244353一个模吧.
#include
#include
#include
#define int long long
#define mod 998244353
using namespace std;
int Max,ans;
signed main(){
scanf("%lld",&Max);
for(int n=1;n<=Max;n++){
for(int i=2,r,l;i<=n;){
l=i; r=n/(n/i);
ans=(ans+(r-l+1)*(n-n/i*i)-(n/i)*(r-l+1)*(r-l)/2)%mod;
i=r+1;
}
}
printf("%lld\n",(ans+mod)%mod);
return 0;
}
题目大意:求 c i = ∑ j = 1 i a ⌊ i j ⌋ b i % j c_i=\sum_{j=1}^{i}a_{\lfloor \frac{i}{j} \rfloor}b_{i\%j} ci=∑j=1ia⌊ji⌋bi%j
上面那个题就是这个题的简化版,上个题有的这个题就不解释了.
#include
#include
#include
#define maxn 100010
#define mod 123456789
#define int long long
using namespace std;
int n,a[maxn],b[maxn],s[maxn][205];
int calc(int n,int x,int l,int r){
int ans=0;
if(n<=200){
if(l-n/x<0) ans=s[r][n/x];
else ans=s[r][n/x]-s[l-n/x][n/x];
}else{
for(int i=x,temp=n/(n/x);i<=temp;i++) ans=(ans+b[n-i*(n/x)])%mod;
}
return (ans+mod)%mod;
}
int solve(int i){
int ans=0;
for(int j=1,r;j<=i;){
r=i/(i/j);
ans=(ans+a[i/j]*calc(i,j,i-r*(i/j),i-j*(i/j)))%mod;
j=r+1;
}
return ans;
}
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
for(int i=0;i<n;i++) scanf("%lld",&b[i]);
for(int i=0;i<n;i++){
for(int j=0;j<=200;j++){
if(i>=j) s[i][j]=(s[i-j][j]+b[i])%mod;
else s[i][j]=b[i];
}
}
for(int i=1;i<=n;i++) printf("%lld\n",solve(i));
return 0;
}
后记:这应该是我人生中第一个与整除分块接触的题,两年前我抱着题解看了一下午也没看懂……这个题noi.ac上好像已经不能评测了,但是这个题我觉得特别棒,如果能过样例,应该能过(我是指大样例),不行的话写对拍也行.
题意:给定 n n n个数 a 1 → a n a_1\to a_n a1→an,求一个最大的 d d d,满足 ∑ i = 1 n d − ( ( a [ i ] − 1 ) % d + 1 ) ≤ k \sum_{i=1}^{n}d-((a[i]-1)\%d+1)\le k ∑i=1nd−((a[i]−1)%d+1)≤k.
最开始想的是二分,结果不是,因为这个 d d d和最终的和(记为 a n s ans ans)根本不是线性的.
所以我们现在开始化简这个式子,但是我们要先知道 % \% %运算怎么变成除运算 x % y = x − y ∗ ⌊ x y ⌋ x\%y=x-y*\lfloor \frac{x}{y} \rfloor x%y=x−y∗⌊yx⌋其实这个也挺好理解的, ⌊ x y ⌋ \lfloor \frac{x}{y} \rfloor ⌊yx⌋就是去掉余数,在乘上就行了,这个应该挺好理解的.所以原式就变成了
∑ i = 1 n d − ( a i − d ∗ ⌊ a i − 1 d ⌋ ) ≤ k d ∗ ( n + ∑ i = 1 n ⌊ a i − 1 d ⌋ ) ≤ k + ∑ i = 1 n a i \sum_{i=1}^{n}d-(a_i-d*\lfloor \frac{a_i-1}{d} \rfloor)\le k\\ d*(n+\sum_{i=1}^{n}\lfloor \frac{a_i-1}{d} \rfloor)\le k+\sum_{i=1}^{n}a_i i=1∑nd−(ai−d∗⌊dai−1⌋)≤kd∗(n+i=1∑n⌊dai−1⌋)≤k+i=1∑nai
到这个地方貌似和整除分块没啥关系….
第一种方法:现在我们需要枚举 d d d,然后 ∑ i = 1 n ⌊ a i − 1 d ⌋ \sum_{i=1}^{n}\lfloor \frac{a_i-1}{d} \rfloor ∑i=1n⌊dai−1⌋其实是只有 m a x { a i } \sqrt {max\ \{a_i\}} max {ai}种方法的,参考整除分块,除下来的结果最多只有 n \sqrt n n个答案的,也就是说我们只需要枚举 1 → n 1\to \sqrt n 1→n就行(这个是其中一种方法,我暂时还没做)
第二种方法:直接把右边 k + ∑ i = 1 n a i k+\sum_{i=1}^{n}a_i k+∑i=1nai加起来(记作 s u m sum sum),再来看左边,当 d ≤ s u m d\le\sqrt {sum} d≤sum时 ( n + ∑ i = 1 n ⌊ a i − 1 d ⌋ ) (n+\sum_{i=1}^{n}\lfloor \frac{a_i-1}{d} \rfloor) (n+∑i=1n⌊dai−1⌋)是必须大于等于 s u m \sqrt{sum} sum的,同理 d ≥ s u m d\ge \sqrt{sum} d≥sum时, ( n + ∑ i = 1 n ⌊ a i − 1 d ⌋ ) (n+\sum_{i=1}^{n}\lfloor \frac{a_i-1}{d} \rfloor) (n+∑i=1n⌊dai−1⌋)是必须小于等于 s u m \sqrt{sum} sum的(不然式子不成立).所以枚举 s u m \sqrt{sum} sum次就行
#include
#include
#include
#define maxn 200
#define int long long
using namespace std;
int n,a[maxn],k,ans,Max;
int calc(int d){
int Ans=0;
for(int i=1;i<=n;i++) Ans+=(a[i]-1)/d;
return Ans;
}
bool check(int d) {return n*d+calc(d)*d<=k;}
signed main(){
scanf("%lld %lld",&n,&k);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]),k+=a[i],Max=max(Max,a[i]);
for(int i=1;i*i<=Max;i++){
if(check(i)) ans=max(ans,i);
if(check(k/i)) ans=max(ans,k/i);
}
printf("%lld\n",ans);
return 0;
}
行啦,讲完了,算是入门了,以后有题在弄上来,听说莫比乌斯反演里面用的多.