社区讨论

莫名其妙对了

P1463[POI 2001 R1 / ZJOI2006 / HAOI2007] 反素数参与者 5已保存回复 4

讨论操作

快速查看讨论及其快照的属性,并进行相关操作。

当前回复
4 条
当前快照
1 份
快照标识符
@mlixskxw
此快照首次捕获于
2026/02/12 12:06
上周
此快照最后确认于
2026/02/14 19:50
5 天前
查看原帖
没有正确性,全靠瞪眼法……考的时候实在是没招了
首先你要会打一个暴力求反素数
CPP
#include<bits/stdc++.h>
using namespace std;
#define ll long long int
bitset<100000010>f;
bool b;
ll prime[6000010],cnt,sig[6000010],k[6000010],maxn,hcn[6000010],tot;//sig存约数个数,k是分解质因数后当前的最小质因数的次幂
int main(){
	hcn[++tot]=1;
	maxn=1;
	for(ll i=2;i<=6000000;i++){
		if(!f[i]){
			prime[++cnt]=i;
			sig[i]=2;
			k[i]=1;
		}
		for(ll j=1,w=i*prime[j];j<=cnt&&w<=6000000;j++,w=i*prime[j]){
			f[w]=true;
			if(i%prime[j]){
				sig[w]=sig[i]*2;
				k[w]=1;
			}else{
				sig[w]=sig[i]/(k[i]+1)*(k[i]+2);
				k[w]=k[i]+1;
				break;
			}
		}
		if(sig[i]>maxn){
			maxn=sig[i];
			hcn[++tot]=i;
		}
	}//改动后的欧拉筛
	for(ll i=1;i<=tot;i++)
		printf("%lld\n",hcn[i]);//输出
	return 0;
}
然后就能得到 1 2 4 6 12 24 36 48 60 120 180 240 360 720 840 1260 1680 2520 5040 7560 10080 15120 20160 25200 27720 45360 50400 55440 83160 110880 166320 221760 277200 332640 498960 554400 665280 720720 1081080 1441440 2162160 2882880 3603600 4324320这些反素数
再浅打一个分解质因数
CPP
#include<bits/stdc++.h>
using namespace std;
#define ll long long int
ll n;
int main(){
	while(true){
		scanf("%lld",&n);
		for(ll i=2;i<=n;i++){
			while(n%i==0){
				printf("%lld ",i);
				n/=i;
			}
		}
		printf("\n");
	}
	return 0;
}
再将这些反素数一个一个输入
CPP
1

2
2
4
2 2
6
2 3
12
2 2 3
24
2 2 2 3
36
2 2 3 3
48
2 2 2 2 3
60
2 2 3 5
120
2 2 2 3 5
180
2 2 3 3 5
240
2 2 2 2 3 5
360
2 2 2 3 3 5
4324320
2 2 2 2 2 3 3 3 5 7 11 13
这些已经够了,然后就瞪眼大法可以发现有两个规律1.除了1之外的反素数,可以被另一个反素数乘上一个质数得到;2.反素数质因数一定是从2开始一个一个出现的,然后发现最大的4324320也不过用到了6个素数的次幂,盲猜最后使用到的素数的个数不超过100
but知道了这两个规律还是有点无从下手怎么办,那就开贪已经贪没边了P2678 [NOIP 2015 提高组] 跳石头正解二分答案,本人一开始打的贪,还贪了50pts
又but贪需要知道当前最优解,怎么比较两个解的优劣呢,再瞪眼,如果一个数的值为val,约数个数为w,那么当val不变的情况下,w越大解肯定越优,反之越劣,当w不变的情况下,val越大肯定越劣,反之越优,而一个数的优劣只受这个数的val和w影响
又又but上述情况比较优劣必须建立在val和w其中一个相同的情况,而且val和w两个数也不好比较,当时本人看时间只有半个多小时了,这道题还没动,还在思考,心一横,直接w/val代表了一个数的优劣程度,满足了上述w越大解越优,反之越劣,val越大越劣,反之越优
然后就可以打了,用一个结构体优先队列存可能的解,比较方式按如上文所说,不过除法要转化成乘法避免精度问题,一开始把反素数2加入队列再按上述规律将2可能产生的所有反素数加入队列,每次取堆顶,判断是否是下一个反素数,不是跳过,是就加入反素数表格

code

CPP
#include<bits/stdc++.h>
using namespace std;
#define ll long long int
inline void in(ll &x){
	x=0;
	bool f=1;
	char c=getchar();
	for(;!isdigit(c);c=getchar())if(c=='-')f=0;
	for(;isdigit(c);c=getchar())x=x*10+(c^48);
	x=f?x:-x;
}
inline void out(ll x){
	if(x<0){putchar('-');x=-x;}
	char buf[20];
	int pos=0;
	if(x==0){putchar('0');return;}
	for(;x;x/=10)buf[pos++]=(x%10)^48;
	while(pos)putchar(buf[--pos]);
}
inline void outel(ll x){
	out(x);putchar('\n');
}//快读快写
ll prime[110]={0,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541};//素数打表
ll cnt=1,n,k[100010][110],maxn,ans_cnt,ans[100010],val_maxn;//给每个反素数打个编号,ans存储反素数,k[i][j]为编号为i的反素数第j个素数的次幂,方便计算约数个数
bool f[110];
map<ll,ll>map_ans;//算是离散化存储已经加入队列的数,防止多次加入
struct node{
	ll val,w,num,ord;//val为值,w为约数个数,num为由这个编号的反素数乘上一个素数得来,ord为乘的哪个编号的素数
};
bool operator < (const node &a,const node &b){
	return a.w*b.val<b.w*a.val;//比较操作
}
priority_queue<node>q;
int main(){
	in(n);
	q.push({2,2,1,1});//将第二个反素数2加入堆顶
	ans[++ans_cnt]=1;
	map_ans[2]=1;//反素数加入一
	while(q.top().val<=n){
		node now=q.top();
		q.pop();//取出当前最优解
		if(now.val<=val_maxn||now.w<=maxn)continue;//判断当前的数是否可能成为下一个反素数
		val_maxn=now.val;
		maxn=now.w;
		ans[++ans_cnt]=now.val;
		for(ll i=1;i<=cnt;i++){
			k[ans_cnt][i]=k[now.num][i];
		}//复制一遍素数的次幂
		k[ans_cnt][now.ord]++;
		if(!f[now.ord]){
			f[now.ord]=true;
			cnt++;
		}//cnt为当前最大的反素数是哪几个不同素数的乘积
		for(ll i=1,j=now.val*prime[i];i<=cnt;i++,j=now.val*prime[i]){
			if(!map_ans.count(j)){
				q.push({j,now.w/(k[ans_cnt][i]+1)*(k[ans_cnt][i]+2),ans_cnt,i});
				map_ans[j]=1;
			}
		}//枚举乘哪个素数,将可能的答案加入优先队列,打上标记
	}//最优解在范围内就一直枚举
	outel(ans[ans_cnt]);//输出答案
	return 0;
}
预计50pts当时发现能找出6000000以内的反素数,太大的暴力枚不了,实际100pts,能得到1~n之间所有反素数,不知道为啥对,将题解代码复制下来,发现就算是超过2e9的数也能对,时间复杂度粗略记一下O(mk),m为反素数的个数,k为最大反素数用到了几个不同的素数相乘,10组样例拢共跑了40ms,快乐的AC了

回复

4 条回复,欢迎继续交流。

正在加载回复...