OI 笑传 #17

1

找到 \(1\sim n\) 中,因数最多的那一个数,如有多个,输出最小的那一个。\(1\le n \le 10^{18}\)

挺有意思的小思维。

首先我们有关于一个正整数 \(n\) 因数的一些定理:

\[n = p_1^{a_1} \times p_2^{a_2} \times \dots \times p_k^{a_k} \]

\(n\)唯一素因数分解式(其中 \(p_i\) 是互不相同的质数,\(a_i \ge 1\))。

\(n\) 的正因数个数为:

\[d(n) = (a_1 + 1)(a_2 + 1) \dots (a_k + 1) \]

\(n\) 的所有正因数之和为:

\[\sigma(n) = \prod_{i=1}^{k} \frac{p_i^{a_i + 1} - 1}{p_i - 1} \]

我们用到第一个式子。

首先根据一个 \(n\) 去确定对应的答案是不好做的。我们尝试定住答案之后构造最小的 \(n\) 使其满足 \(d(n)=ans\)

有一个观察是 \(ans \le 10^6\),这个应该是显然的。

构造的方法就是枚举 \(ans\) 的所有分解方法,这样就可以对应到每一个 \(a_i\),就可以求出 对应的 \(n\)。然后这好像还是很难办。

于是能不能直接构造对应最小的 \(n\) 使其满足 \(d(n)=ans\)

继续观察,因为因数和式子的形式,里面每一种质因数都是平等的,也就是只与这个质因数的指数有关系。

于是我猜所有最小的 \(n\) 拥有的质因数的最大值一定不会太大,不然当然可以换成一个更小的,因为每一种质因数都是平等的。

不会太大到哪里去?我们从 \(2\) 开始乘质数,你会发现最多乘到 \(57\) 就过 \(10^{18}\) 了。

于是我们只需要 \(2\sim 57\) 这些质数去构造 \(n\) 就行了。

怎么构造?搜索。

而且还有个观察,我们用 \(2\sim 57\) 这些数的次数是单调不升的,还是因为每一种质因数都是平等的,且我们还要让 \(n\) 最小。

于是加上这个剪枝,试了下发现跑得飞快。

把搜出来的最小的 \(n\) 及其答案存到数组里,再做一遍前缀最大值,查询的时候二分即可。

code

Show me the code
#define psb push_back
#define mkp make_pair
#define ls p<<1
#define rs (p<<1)+1
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#define rd read()
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read(){
  ll x=0,f=1;
  char c=getchar();
  while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
  while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
  return x*f;
}
int s[1000]={0,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59};
int cnt=0;
int setl[100];
const __int128 lm=1e18;
vector<pair<ll,int> > pans;
void dfs(int p,__int128 ct,ll res){
  if(p>17)return ;
  while(setl[p]+1<=setl[p-1]&&ct*s[p]<=lm){
    if(setl[p]!=0)res/=setl[p];
    setl[p]++;
    res*=setl[p];
    ct*=s[p];
    pans.push_back(mkp(ct,res));
    dfs(p+1,ct,res);
  }
  setl[p]=1;return ;
}
vector<pair<ll,int> > pr;
int main(){
  
  setl[0]=114514;
  for(int i=1;i<=19;i++)setl[i]=1;
  dfs(1,1,1);
  sort(pans.begin(),pans.end());
  int qmax=1;
  pr.push_back(mkp(1,1));
  for(int i=0;i<pans.size();i++){
  	if(pans[i].second>qmax){
  		pr.push_back(mkp(pans[i].first,pans[i].second));
  		qmax=max(qmax,pans[i].second);
		}
	}
	
	int T;cin>>T;
	while(T--){
		ll rr;cin>>rr;
		int p=lower_bound(pr.begin(),pr.end(),mkp(rr,0))-pr.begin();
		if(pr[p].first==rr){
			cout<<pr[p].first<<' '<<pr[p].second<<'\n';
		}
		else
			cout<<pr[p-1].first<<' '<<pr[p-1].second<<'\n';		
	}
  
  return 0;
}

2

\(n\) 个作业,每个作业 \(i\) 有两个整数属性:

  • 难度 \(a_i\)
  • 限制值 \(b_i\)

定义一个作业执行顺序为排列:

\[S = (s_1, s_2, \dots, s_n) \]

该排列是有效的,若同时满足以下两个条件:

1️⃣ 难度非递减:

\[a_{s_1} \le a_{s_2} \le \dots \le a_{s_n} \]

2️⃣ 邻近限制:
对于任意相邻作业 \((s_i, s_{i+1})\),定义:

\[C(i, i+1) = |\{k \mid s_k \in \{s_1, \dots, s_i\}, \; \min(a_{s_i}, a_{s_{i+1}}) \le a_{s_k} \le \max(a_{s_i}, a_{s_{i+1}})\}| \]

要求:

\[C(i, i+1) - 1 \le b_{s_i} \]

求所有满足条件的作业顺序数量,对 \(4921057\) 取模。

Input

  • 第一行:整数 \(n\)
  • 第二行:\(n\) 个整数 \(a_1, a_2, \dots, a_n\)
  • 第三行:\(n\) 个整数 \(b_1, b_2, \dots, b_n\)

Output

输出一个整数,为方案总数模 \(4921057\)

Constraints

\[1 \le n \le 18 \]

\[0 \le a_i, b_i \le 10^9 \]

简单状压。马力即可。

用了 __builtin_popcount() 算二进制 \(1\) 的数量。

code

Show me the code
#define psb push_back
#define mkp make_pair
#define ls p<<1
#define rs (p<<1)+1
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#define rd read()
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read(){
  ll x=0,f=1;
  char c=getchar();
  while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
  while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
  return x*f;
}
const int mod=4921057;
const int N=(1<<19);
const int M=20;
int dp[N][20];
int a[M],b[M];
vector<int> st[M];
int main(){
  
  int n;cin>>n;
  for(int i=0;i<n;i++)cin>>a[i];
  for(int j=0;j<n;j++)cin>>b[j];
  for(int i=0;i<(1<<n);i++){
    int cnt=__builtin_popcount(i);
    st[cnt].push_back(i);
    if(cnt==1){
      for(int j=0;j<n;j++){
        if((i>>j)&1)dp[i][j]=1;
      }
    }
  }
  for(int i=2;i<=n;i++){
    for(int s:st[i]){
      vector<int> p;
      int cnt[30];
      p.clear();memset(cnt,0,sizeof cnt);
      for(int j=0;j<n;j++){
        if((s>>j)&1)p.push_back(j);
        else{
        	cnt[j+1]=1;
				}
        cnt[j+1]=cnt[j+1]+cnt[j];	
      }
      for(int u:p){for(int v:p){
        if(u==v||dp[s^(1<<v)][u]==0)continue;
        if(a[v]<a[u])continue;
        if(u<v){
          if(cnt[v+1]-cnt[u]>b[v])continue;
        }
        if(u>v){
          if(cnt[u+1]-cnt[v]>b[v])continue;
        }
        dp[s][v]=(dp[s^(1<<v)][u]+dp[s][v])%mod;
      }}
    }
  }
  int ans=0;
  for(int i=0;i<n;i++){
    if(dp[(1<<n)-1][i]!=0)
      ans+=dp[(1<<n)-1][i];
      ans=ans%mod;
  }
  cout<<ans;
  
  return 0;
}

想起来去年 CSPS 上整的活了,今年还想整活。

posted @ 2025-10-10 17:27  hm2ns  阅读(6)  评论(0)    收藏  举报