结论+生成函数优化dp+NTT+启发式
个人感觉这道题非常好!非常精妙!
1.首先我们要知道一个结论:
我们把这个数的约数按拥有的质因子数(可重)划分,第i类约数表示有i个可以重复的质因子
然后我们从每一级的约数a向它的下一级约数b(a%b==0 且b只比a少一个质因子)连边,那么这个图显然是个DAG
显然如果我想找最大的可选的约数,那么从n级到1级的任意一条链上只能选一个值
然后经证明可得我选n/2级的时候所选的数集最大
证明:https://pure.tue.nl/ws/files/4373475/597494.pdf
2.那么现在问题就转变为我想求出每一级有多少个点(即约数)
这就是一个 生成函数优化dp 经典的问题了
对于每个不同得到质因子,假设是第i种质因子,并且个数有k个
我们设g(x)=1+x^1+x^2+,,,,,,+x^k 为这种因子的多项式
那么最后的答案多项式一定是形如f(x)=a[1]+a[2]*x^1+a[3]*x^2+......+a[n+1]*x^n这样的多项式
并且f(x)为所有g(i)的卷积
所有g(i)的卷积我们显然可以用NTT快速求出
3当然如果暴力NTT的话显然是n^2log的(因为你可能每次都取出最长的那个多项式和最短的多项式合并,这样每次都是nlog,最多做n次)
那么我们考虑用启发式的思想来优化它
我们可以很快想到可以每次找出长度最小的那2个子串来做NTT,这样每做一次NTT后,一个多项式的长度至少会增大一倍。
那么考虑最初的某个串,它最多增大log次就会到最初的长度,因为最后卷积出来的函数长度不会超过n(总长才n)
所以均摊下来是log的,然后算上NTT的一个log,总复杂度就优化到nlog^2
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <queue>
#include <utility>
#define fr first
#define sc second
#define mp make_pair
#define pb push_back
using namespace std;
const int P=998244353;
const int N=1e6+10;
const int maxn=1e6+10;
int fexp(int a,int n)
{
int res=1;
for (;n;n>>=1,a=1ll*a*a%P) if (n&1) res=1ll*res*a%P;
return res;
}
struct da
{
int n,w[2][N];
void init(int lim)
{
for (n=1;n<lim;n<<=1);
w[0][0]=w[0][n]=1;
int g=fexp(3,(P-1)/n);
for (int i=1;i<n;i++) w[0][i]=1ll*w[0][i-1]*g%P;
for (int i=0;i<=n;i++) w[1][i]=w[0][n-i];
}
void ntt(int *a,int v)
{
for (int i=0,j=0;i<n;i++)
{
if (i>j) swap(a[i],a[j]);
for (int l=n>>1;(j^=l)<l;l>>=1);
}
for (int l=2;l<=n;l<<=1)
{
int m=l>>1;
for (int i=0;i<n;i+=l)
for (int k=0;k<m;k++)
{
int t=1ll*w[v][n/l*k]*a[i+k+m]%P;
a[i+k+m]=(a[i+k]-t>=0)?(a[i+k]-t):(a[i+k]-t+P);
a[i+k]=(a[i+k]+t<P)?(a[i+k]+t):(a[i+k]+t-P);
}
}
if (!v) return;
int rv=fexp(n,P-2);
for (int i=0;i<n;i++) a[i]=1ll*a[i]*rv%P;
}
void work(int *a,int *b,int *c,int m)
{
init(m); ntt(a,0); ntt(b,0);
for (int i=0;i<n;i++) c[i]=1ll*a[i]*b[i]%P;
ntt(c,1);
}
}NTT;
int n,lc[10000010],cnt,v[maxn],a[maxn],b[maxn],c[maxn];
vector<int>d[maxn];
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >q;
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
int xx=0; scanf("%d",&xx);
if (!lc[xx]) lc[xx]=++cnt,d[cnt].pb(1);
d[lc[xx]].pb(1);
}
for (int i=1;i<=cnt;i++) q.push(mp(d[i].size(),i));
while (q.size()!=1)
{
int id1=q.top().sc,len1=d[id1].size(); q.pop();
int id2=q.top().sc,len2=d[id2].size(); q.pop();
for (int i=0;i<len1;i++) a[i]=d[id1][i];
for (int i=0;i<len2;i++) b[i]=d[id2][i];
NTT.work(a,b,c,len1+len2);
for (int i=0;i<=2*(len1+len2)+100;i++) a[i]=0;
for (int i=0;i<=2*(len1+len2)+100;i++) b[i]=0;
d[id1].clear();
for (int i=0;i<len1+len2-1;i++) d[id1].pb(c[i]);
q.push(mp(len1+len2-1,id1));
for (int i=0;i<=2*(len1+len2)+100;i++) c[i]=0;
}
int kk=q.top().sc;
printf("%d\n",d[kk][n>>1]);
return 0;
}