【口胡】
n个正整数乘起来,把这个乘积分解质因数得到:(2^p1)*(3^p2)*(5^p3)*(7^p4)*......
然后,把m的阶乘分解质因数得到:(2^q1)*(3^q2)*(5^q3)*(7^q4)*......
【没有这个质因数就是0次方】
如果有p[i]大于q[i],那么这个阶乘就是不合法的。因为这个乘积无论乘以哪个正整数都无法得到这个阶乘。
二分答案判断就行了。【注意二分的上界,尽量大一点】
#include<bits/stdc++.h>
using namespace std;
const int maxn=100010;
int P[maxn],sum[maxn],Sum[maxn],tot=0,n,a,END;
bitset<maxn> prime;
void pre(){
prime[0]=prime[1]=1;
for(int i=2;i<=100000;++i){
if(!prime[i]){
for(int j=i+i;j<=maxn;j+=i) prime[j]=1;
P[++tot]=i;
}
}
}
void fenjie(int x){
if(x==1||x==0) return;
for(int i=1;P[i]<=x&&i<=tot;++i){
while(x%P[i]==0){
x/=P[i];
sum[i]++;
}
END=max(END,i);
}
}
int check(int x){
memset(Sum,0,sizeof(Sum));
for(int i=1;P[i]<=x&&i<=tot;++i){
int tmp=x;
while(tmp){
tmp/=P[i];
Sum[i]+=tmp;
}
}
for(int i=1;i<=END;++i)
if(sum[i]>Sum[i]) return 0;
return 1;
}
int main(){
/* freopen("fact.in","r",stdin);
freopen("fact.out","w",stdout);*/
pre();
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%d",&a);
fenjie(a);
}
int l=1,r=1e9;
while(l<r){
int mid=(l+r)/2;
if(check(mid)) r=mid;
else l=mid+1;
}
cout<<l<<endl;
}
————————————————————————————————————————————————————————
【数据范围与约定】
对于前 30% 的数据, n ≤ 9;
对于前 60% 的数据, n ≤ 12;
对于 100% 的数据, 1 ≤ m ≤ n ≤ 15.
【口胡】
首先看数据范围,想到状压DP。
一个状态S存储所有数的取用情况,一个状态S1存储A数组的取用情况。
但是如果用f[S][S1]存储,时空都无法承受,考虑用三进制。
0表示该数没有被选。1表示这一位被选,但不是A中的数。2表示这一位被选,且是A中的数。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int pow2[20],pow3[20];
int n,m,a[20];
ll f[14348908],ans=0;
//f[i]表示当前状态为i时,合法情况数目
//sta描述总状态。
//cnt记录当前在A数组中选了多少个。
void dfs(int pos,int cnt,int sta){
if(pos==n+1){
if(!cnt)//如果满足A中选了m个数,那么这个情况是合法的
ans+=f[sta];
return;
}
//当前取的数不在A中,那么sta对应位置上是1.
sta+=pow3[pos-1];
dfs(pos+1,cnt,sta);
//当前取的数在A中,那么sta对应位置上是1+1,即是2.
sta+=pow3[pos-1];
dfs(pos+1,cnt-1,sta);
//其实就是枚举每一位是1还是2.
}
//check函数判断该情况合不合法。
//如果取了A数组中的一个数而这时它前面的数没有被取,则不合法。
//例如:1 3 4,如果现在取了3而没有取1,则不合法。
//x为当前取的所有数的状态。
bool check(int x){
bool flag=1;
for(int i=1;i<=m;++i)//枚举A数组中的每个数
if((x&pow2[a[i]-1])==0) flag=0;
//pow2[i]即2的i次方。 x&pow2[a[i]-1]即看a[i]是否被取。
//如果当前的数没有被取,那么A数组在它后面的数就不能取了,此时把flag记为0.
//否则如果当前的数被取了,并且flag为0,则该情况不合法。
else if(!flag) return false;
return true;
}
int main(){
pow2[0]=pow3[0]=1;
for(int i=1;i<=16;++i) pow2[i]=pow2[i-1]*2,pow3[i]=pow3[i-1]*3;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i) scanf("%d",a+i);
f[0]=1;
//i表示取的所有数的状态
for(int i=0;i<pow2[n];++i){
if(!check(i)) continue;
int sub=i;
//sub记录的是A数组中的取用情况
for(int sub=i;;sub=(sub-1)&i) //遍历i的子集
{
int sta=0;
for(int j=1;j<=n;++j){
if(sub&pow2[j-1]) sta+=pow3[j-1]; //sub对应出来就是2
if(i&pow2[j-1]) sta+=pow3[j-1]; //sta加出来就是当前状态
}
//从这个状态向其他状态转移
if(f[sta]){
for(int j=1;j<=n;++j){
//如果第j位还没有数
//假设第j位取了A中的一个数,那么新的状态就是msta,然后遍历后面的每一位
//若后面的位置上有2,找到第一个为2的,把它退掉
//这个思想和维护最长不下降序列的思想类似
if(sta/pow3[j-1]%3==0){
int msta=sta+2*pow3[j-1];
for(int k=j+1;k<=n;++k){
if(msta/pow3[k-1]%3==2){
msta-=pow3[k-1];
break;
}
}
//这时,取j的情况msta可以由不取j的情况sta转移过来。
f[msta]+=f[sta];
}
}
}
//子集枚举完就退出
if(!sub) break;
}
}
dfs(1,m,0);
cout<<ans<<endl;
}