P1520 因式分解 题解
【分析】
定义分圆多项式 \(\displaystyle \Phi_n(x)=\prod_{i=0}^{n-1} (x-\omega_n^i)^{[\gcd(n, i)=1]}\) ,其中 \(\omega_n^i\) 为 \(n\) 次单位根。
可以证明的是,所有的分圆多项式都是整数域上不可再分解的素多项式。
我们观察式子显然能发现,该分圆多项式的最高次为欧拉函数 \(\boldsymbol \varphi(n)\) ,且系数必定为 \(1\) 。
而这个式子有什么用呢?
考虑 \(x^n-1=0\) 的所有解,恰好为 \(\omega_n^0,\omega_n^1,\cdots, \omega_n^{n-1}\) 。
因此,显然有:
\(\begin{aligned} x^n-1&=\prod_{i=0}^{n-1}(x-\omega_n^i) \\&=\prod_{d\mid n}\prod_{i=0}^{n-1}(x-\omega_n^i)^{[\gcd(n, i)=d]} \\&=\prod_{d\mid n}\prod_{i=0}^{{n\over d}-1}(x-\omega_{n\over d}^i)^{[\gcd({n\over d}, i)=1]} \\&=\prod_{d\mid n}\Phi_{n\over d}(x) \end{aligned}\)
即 \(x^n-1\) 在整数域上分解为素多项式,即需要输出 \(n\) 的所有因数 \(d\) 对应的分圆多项式 \(\Phi_d(x)\) 。
分圆多项式 \(\Phi_n(x)\) 的求法则是根据上面那个式子:
\(\begin{aligned} x^n-1&=\prod_{d\mid n}\Phi_d(x) \\\ln(x^n-1)&=\sum_{d\mid n}\ln \Phi_d(x)&(\text{两边取 }\ln) \\\ln\phi_n(x)&=\sum_{d\mid n}\ln(x^d-1)\cdot \boldsymbol \mu({n\over d})&(\text{莫比乌斯反演}) \\\Phi_n(x)&=\prod_{d\mid n}(x^d-1)^{\boldsymbol \mu({n\over d})}&(\text{两边取 }\exp) \\\Phi_n(x)&=(-1)^{\sum_{d\mid n}\boldsymbol \mu(d) }\cdot \prod_{d\mid n}(1-x^{n\over d})^{\boldsymbol \mu(d)} \\\Phi_n(x)&=(-1)^{[n=1]}\cdot \prod_{d\mid n}(1-x^{n\over d})^{\boldsymbol \mu(d)} \end{aligned}\)
考虑到 \(\boldsymbol \mu(d)\) 的取值只有 \(-1, 0, 1\) 三种,不妨分类讨论一下:
\(\boldsymbol \mu(d)=0\) 时,\((1-x^{n\over d})^0\) 对 \(\Phi_n(x)\) 的计算没有意义,直接省略。
\(\boldsymbol \mu(d)=1\) 时,\((1-x^{n\over d})\) 对 \(\Phi_n(x)\) 的计算等价于跑一个01背包。
\(\boldsymbol \mu(d)=-1\) 时,\({1\over 1-x^{n\over d}}\) 对 \(\Phi_n(x)\) 的计算等价于跑一个完全背包。
我们分类存储一下每个 \(\boldsymbol \mu(d)\) 非零的 \(d\) ,对于倍数 \(kd\) 对应的分圆多项式 \(\Phi_{kd}(x)\) 贡献的 \((1-x^k)\) 究竟是01背包还是完全背包。
单独把 \(\Phi_1(x)\) 的符号取反一下就可以了。
设 \(\omega(n)\) 表示 \(n\) 的不同质因子数,则求解 \(\Phi_n(x)\) 时,由于莫比乌斯函数的容斥,最多只会跑 \(2^{\omega(n)}\) 次的背包,每次背包的容积又为 \(\boldsymbol \varphi(n)\) 。故求解单个 \(\Phi_n(x)\) 的复杂度为 \(O(2^{\omega(n)}\cdot \boldsymbol \varphi(n))\) 。
故求解的总复杂度为 \(O(\displaystyle \sum_{d\mid n}2^{\omega(d)}\cdot \boldsymbol \varphi(d))\) 。
由于 \(\omega(n)\) 为加性函数,\(\boldsymbol \varphi(n)\) 为积性函数;故 \(2^{\omega(n)}\) 为积性函数;由两个积性函数的点积为积性函数,两个积性函数的迪利克雷卷积也为积性函数;故复杂度的这个函数是个积性函数,可以由线筛得出。
经过筛法求解,这个函数在 \(5\times 10^3\) 范围内,最大值为 \(110565\) 。
【代码】
注意一下多项式的排序方式和输出规范即可。
#include <bits/stdc++.h>
using namespace std;
constexpr int Lim=1e5, MAXN=Lim+10;
int prime[MAXN], cntprime, fc[MAXN], phi[MAXN], mu[MAXN];
inline void sieve() {
phi[1]=mu[1]=1;
for(int i=2; i<=Lim; ++i) {
if(!fc[i]) {
fc[i]=prime[++cntprime]=i;
phi[i]=i-1;
mu[i]=-1;
}
for(int j=1; j<=cntprime; ++j)
if(prime[j]*i>Lim) break;
else if(prime[j]==fc[i]) {
fc[prime[j]*i]=prime[j];
phi[prime[j]*i]=prime[j]*phi[i];
mu[prime[j]*i]=0;
break;
}
else {
fc[prime[j]*i]=prime[j];
phi[prime[j]*i]=phi[prime[j]]*phi[i];
mu[prime[j]*i]=-mu[i];
}
}
}
vector<int> con[MAXN][2], d[MAXN];
inline void init() {
sieve();
for(int i=1; i<=Lim; ++i) if(mu[i])
for(int j=i, k=1; j<=Lim; j+=i, ++k)
con[j][mu[i]==1].push_back(k);
for(int i=1; i<=Lim; ++i)
for(int j=i; j<=Lim; j+=i)
d[j].push_back(i);
}
struct poly : vector<int> {
inline bool operator < (const poly &x) const {
if(size()!=x.size())
return size()<x.size();
for(int i=size()-1; i>=0; --i)
if(abs(at(i))!=abs(x[i]))
return abs(at(i))<abs(x[i]);
else if((at(i)<0)^(x[i]<0))
return at(i)<0;
return 0;
}
inline friend ostream& operator << (ostream& out, const poly &p) {
out<<"(";
for(int I=p.size()-1, i=I; i>=0; --i) {
if(p[i]==0) continue;
if(i!=I&&p[i]>0) out<<"+";
if(i==0) {
out<<p[i];
continue;
}
if(p[i]==-1) out<<"-";
else if(p[i]!=1) out<<p[i];
if(i>1) out<<"x^"<<i;
else if(i==1) out<<"x";
}
return out<<")";
}
}p[MAXN];
bool vis[MAXN];
inline void calc(int n) {
poly& phin=p[n];
phin.resize(phi[n]+1);
phin[0]=1;
for(auto e : con[n][1])
for(int i=phi[n]; i>=e; --i)
phin[i]-=phin[i-e];
for(auto e : con[n][0])
for(int i=e, I=phi[n]; i<=I; ++i)
phin[i]+=phin[i-e];
vis[n]=1;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
init();
calc(1); for(auto &e : p[1]) e=-e;
int T=1, n;
while(T--&&cin>>n) {
if(n==1) {
cout<<"x-1\n";
break;
}
for(auto e : d[n])
if(!vis[e])
calc(e);
sort(d[n].begin(), d[n].end(), [](int a, int b) { return p[a]<p[b]; });
for(auto e : d[n])
cout<<p[e];
cout<<"\n";
}
cout.flush();
return 0;
}
关于双倍经验的题目(其实我是先做了双倍经验的题目再回来做这题的),由于数据范围更大,还加入了 \(T\) 组数据,如果暴力跑的话可能复杂度不够。
同上文的分析,在跑满的情况下,复杂度最劣会达到 \(5014548\times 100\approx 5\times 10^8\) ,复杂度比较危险。
本人的处理方式是,先 \(O(n\log n)\) 预处理,把每个 \(\boldsymbol \mu(d)\) 不为零的 \(d\) 对倍数 \(kd\) 贡献的到底是 \(k\) 的01背包还是完全背包先记录下来。
后续询问的过程中,用 vis
数组表示这个分圆多项式的最终答案是否已经计算过了。对于每个询问的 \(n\) ,我去访问它的所有因子,把它因子中没处理过的分圆多项式给算出来。
虽然最劣的复杂度和把所有分圆多项式都跑一遍没有区别,但是在随机数据的情况下会更优。
注意两题的排序方式不同。
#include <bits/stdc++.h>
using namespace std;
constexpr int Lim=1e5, MAXN=Lim+10;
int prime[MAXN], cntprime, fc[MAXN], phi[MAXN], mu[MAXN];
inline void sieve() {
phi[1]=mu[1]=1;
for(int i=2; i<=Lim; ++i) {
if(!fc[i]) {
fc[i]=prime[++cntprime]=i;
phi[i]=i-1;
mu[i]=-1;
}
for(int j=1; j<=cntprime; ++j)
if(prime[j]*i>Lim) break;
else if(prime[j]==fc[i]) {
fc[prime[j]*i]=prime[j];
phi[prime[j]*i]=prime[j]*phi[i];
mu[prime[j]*i]=0;
break;
}
else {
fc[prime[j]*i]=prime[j];
phi[prime[j]*i]=phi[prime[j]]*phi[i];
mu[prime[j]*i]=-mu[i];
}
}
}
vector<int> con[MAXN][2], d[MAXN];
inline void init() {
sieve();
for(int i=1; i<=Lim; ++i) if(mu[i])
for(int j=i, k=1; j<=Lim; j+=i, ++k)
con[j][mu[i]==1].push_back(k);
for(int i=1; i<=Lim; ++i)
for(int j=i; j<=Lim; j+=i)
d[j].push_back(i);
}
struct poly : vector<int> {
inline bool operator < (const poly &x) const {
if(size()!=x.size())
return size()<x.size();
for(int i=size()-1; i>=0; --i)
if(at(i)!=x[i])
return at(i)<x[i];
return 0;
}
inline friend ostream& operator << (ostream& out, const poly &p) {
out<<"(";
for(int I=p.size()-1, i=I; i>=0; --i) {
if(p[i]==0) continue;
if(i!=I&&p[i]>0) out<<"+";
if(i==0) {
out<<p[i];
continue;
}
if(p[i]==-1) out<<"-";
else if(p[i]!=1) out<<p[i];
if(i>1) out<<"x^"<<i;
else if(i==1) out<<"x";
}
return out<<")";
}
}p[MAXN];
bool vis[MAXN], sorted[MAXN];
inline void calc(int n) {
poly& phin=p[n];
phin.resize(phi[n]+1);
phin[0]=1;
for(auto e : con[n][1])
for(int i=phi[n]; i>=e; --i)
phin[i]-=phin[i-e];
for(auto e : con[n][0])
for(int i=e, I=phi[n]; i<=I; ++i)
phin[i]+=phin[i-e];
vis[n]=1;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
init();
calc(1); for(auto &e : p[1]) e=-e;
int T, n; cin>>T;
while(T--&&cin>>n) {
for(auto e : d[n])
if(!vis[e])
calc(e);
sort(d[n].begin(), d[n].end(), [](int a, int b) { return p[a]<p[b]; });
for(auto e : d[n])
cout<<p[e];
cout<<"\n";
}
cout.flush();
return 0;
}