题目背景
超水的签到题
题目描述
DLS 有个花田,每个花田里有
朵花。
DLS 喜欢稀奇古怪的花田,他希望重新排列花田,然后去采花。
但 DLS 采花又有一个癖好:他会从左往右采花。
若当前采到第个花田,在之前有一个花田的花的数量,是第
个花田的花的数量的因子的话,那么 DLS 不会采这个花田的花。
现在,DLS 想知道对于所有排列花田的方案,他能够采到的花的数量的和是多少。
由于答案会比较大,请对取模。
输入输出格式
输入格式:
第一行一个正整数。
第二行是一个长度为的序列
。
输出格式:
共一行,表示所有方案中采花的数量和对取模的结果。
题目来源:https://www.luogu.org/problemnew/show/P5216
Solution(由暴力到正解)
1、枚举所有排列,时间复杂度为,能通过50%的数据
;
2、我们换一种思路,统计每个花田的贡献值;
所谓贡献值,就是每种花田对答案贡献了多少,它由花田中花的数量以及花田被统计的次数决定,例如:
在输入为2 3 4中,有六种排列:
2 3 4
2 4 3
3 2 4
3 4 2
4 2 3
4 3 2
对于花的数量为4的花田的贡献值,我们发现:在第四、五、六种排列中它被统计入采花数量和(因一、二、三种排列中4的前面均有2这个因子),因此它的贡献值为.
那么如何求贡献值呢?
我们考虑一下一个花田要满足什么条件才能被统计,显然地,要把它的因子全放在这块花田的后面。因此,这就是一个组合数学问题了。
设一个花田在这块花田中因子数目为
,且这块花田含
朵花;我们可以枚举这个花田应该放的位置,并在后面选
个位置放置它的因子,然后将所有花田排列一下,那么就是
,预处理阶乘逆元后,这里运算用到的时间复杂度为
,由于有
块花田,所以求解总时间复杂度为
.
那么如何求得呢?直接暴力枚举同样也是
,因此只能通过80%的数据
,而最大数据
,我们需要更优的方法。
3、看到其它题解中有用桶排序的方法,时间复杂度为,我就分享下我的
的做法吧!
先说说求的方法:用
数组记录每个数字出现的次数,然后枚举每块花田,但是第二重循环并非暴力地枚举其它花田,而是枚举数字找因子——这样可以保证只枚举到
来实现优化目的;因为枚举到的每个因子对应着两个因子,例如通过求得
的因子
,可以知道它还对应着另一个因子
,因此我们只需枚举到
,也就是说可以在
时间内把一个数的所有因子统计出来,然后加上每个因子出现的次数即为因子总数;值得注意的是,在因子为
和刚好为
时有特殊情况需判断,详见代码;
那么对于求解的过程怎么优化呢?其实我们不必枚举某个花田的位置,可以直接利用组合先选出个位置放置该花田即它的因子,然后排列一下,每块花田的贡献值就是就是:
,这样就可以在
时间内求解答案。
总时间复杂度为,期望得分100分。
#include<cstdio>
#include<cmath>
#define mod 998244353
using namespace std;
int n;
int a[100001];
int x[100001];
int num[100001];
long long mul[100001];
long long inverse[100001];
long long ans;
long long C(long long a,long long b)
{
long long t=1;
t=t*mul[b]%(long long)mod;
t=t*inverse[a]%(long long)mod;
t=t*inverse[b-a]%(long long)mod;
return t;
}
int main()
{
scanf("%d",&n);
mul[0]=1;
mul[1]=1;
for (int i=2;i<=100000;i++) //预处理阶乘
mul[i]=mul[i-1]*(long long)i%(long long)mod;
inverse[0]=1;
inverse[1]=1;
for (int i=2;i<=100000;i++) //预处理逆元
inverse[i]=(long long)(mod-mod/i)*inverse[mod%i]%(long long)mod;
for (int i=2;i<=100000;i++) //预处理阶乘逆元
inverse[i]=inverse[i-1]*inverse[i]%(long long)mod;
for (int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
num[a[i]]++;
}
for (int i=1;i<=n;i++)
for (int j=1;j<=int(sqrt(a[i]));j++) //枚举因子
if (a[i]%j==0)
{
if (a[i]==1&&j==1) //特殊情况1:因子和花的数目都为1
{
x[i]+=num[1]-1;
break;
}
else if (j==1) x[i]--; //特殊情况2:因子为1,花的数目不为1
else if (j*j==a[i]) //特殊情况3:因子是花的数目的算术平方根
{
x[i]+=num[j];
break;
}
x[i]+=num[j]+num[a[i]/j];
}
for (int i=1;i<=n;i++)
{
ans+=(long long)(a[i])*C(x[i]+1,n)%mod*mul[x[i]]%mod*mul[n-x[i]-1]%mod;
ans%=mod;
}
printf("%lld",ans);
}