NOIP 2018.10.5

【口胡】

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;
}

猜你喜欢

转载自blog.csdn.net/g21wcr/article/details/82945192