题解 CF822D 【My pretty girl Noora】

· · 题解

来一发全场最慢解,总共跑了 \rm 1.16 \min ,但是思路简单,代码好写。

n\log n 大力跑过 5 \times 10^6,本题时限 \rm 1.5 \ s 我最慢的一个点跑了 \rm 1497 \ ms

题意:

+ 设当前人数为 $m$ ,选择一个正整数 $x(m≥x>1,x ∣ m)$ ,划分为 $\frac{m}{x}$ 组,每组 $x$ 个人。 + 每一组中的女生两两比较,共进行 $\frac{x\times(x-1)}{2} $ 次比较。最终每一组只会有 $1$ 名女生晋级。 + 所有晋级的女生继续进行选美,直到只剩下一名女生。 设初始为 $n$ 名女生时最少的总比较次数为 $f(n)$,求 $$\sum_{i=l}^r t^{i-l}\times f(i) \mod (10^9+7)$$ --- 其实就是批量求一个 $f(i)$ 然后最后用一种奇怪的方式求和之后输出。 开始,我们可以有一个幼稚的想法,直接枚举因数取 $\min$ 。式子是这个: $$f(i) = \min _{d|n} \frac{n}{d}\times \frac{d(n-1)}{2} \times f(\frac{n}{d})$$ 其实 $\sum_{i=1}^n \sum_{d|i} 1$ 是 $O(n \log n )$ 的,但我们直接枚举因数是 $O(n\sqrt n)$ 的,考虑如何优化这个东西。 我们枚举复杂度不够优的原因是枚举了大量不是因数的东西,所以我们把式子搞成递推的形式,就可以直接枚举倍数而不多枚举了。这个是递推式: $$f(i\times j)=\min (f(i \times j),f[i]+ \frac{j\times i \times (j-1)}{2})$$ 其实这个转换挺常见的,然后枚举代码变成 ```cpp for(int i=1;i<=n;i++) for(int j=2;j*i<=n;j++) ``` 就是 $O(n\log n )$ 的了,就可以(靠着cf的评测机)大力艹过去了。 (这个 $O(n\log n )$ 有两种方式理解,一种是考虑意义是这个$\sum_{i=1}^n \sum_{d|i} 1$ ,还有一种是直接写式子发现是个调和级数求和就可以推出来了) --- 代码: ```cpp #include<bits/stdc++.h> inline int R() { char c;int sign=1,res=0; while((c=getchar())>'9'||c<'0') if(c=='-') sign=-1;res+=c-'0'; while((c=getchar())>='0'&&c<='9') res=res*10+c-'0'; return res*sign; } using namespace std; #define int long long const int Maxm=5e6+5; const int Mo=1e9+7; int n,m,f[Maxm],t,l,r; signed main() { memset(f,0x7f,sizeof f); t=R();l=R();r=R();n=r; f[1]=0; for(int i=1;i<=n;i++) for(int j=2;j*i<=n;j++) f[i*j]=min(f[i*j],f[i]+1ll*j*i*(j-1)/2); int ans=0,S=1; for(int i=l;i<=r;i++) { ans=(ans+S*(f[i]%Mo))%Mo; S=S*t%Mo; } printf("%lld\n",ans); } ```