P11314 [RMI 2021] 礼物 / Present 题解
__Silvefish__ · · 题解
P11314 [RMI 2021] 礼物 / Present 题解
一个正整数集合
S 是好的当且仅当\forall a,b \in S ,\gcd(a,b) \in S 。定义S 的权值为\sum\limits_{a \in S} 2^{a-1} 。求出权值第k 小的好的集合。k \le 1.5 \times 10^9 。
在这个数据范围下,我们可以感觉到答案集合的最大值并不会太大。因此,我们可以尝试直接把答案搜出来。具体地,假设答案集合的最大值为
不过,显然
我们考虑在选完较大的数之后,这些已经确定选的数对那些还没确定要不要选的数产生的限制。这时候,我们就会发现,不同的大数选择情况对小数的限制可能是相同的。例如,假设大于
具体地,可以发现,我们在考虑
因此,搜索的时候就只需要记录:所有还没有确定选不选的数中,已经确定要选的数中它们的倍数的最大公因数是多少。对于一个还没有确定选不选的数
我们在记忆化搜素的时候,记录
预处理结束后,对于一个给定的
如果你写完代码去跑一遍极限数据,就会发现答案集合的最大值不超过
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n=37,m=n/3,k;
int gcd[45][45];
int bas[20];
unordered_map<int,int> mp[45];
//msk[i] : 目前选的数中 i 的倍数的所有数的 gcd 是 i 的几倍
int dfs1(int x,int msk)
{
if(!x) return mp[x][msk]=1;
if(mp[x].count(msk)) return mp[x][msk];
int ret=0,_=msk;
if(x>m || msk/bas[x-1]%(n/x+1)!=1) ret+=dfs1(x-1,msk);
for(int i=1;i<=m;i++)
{
if(x%i) continue;
int k=msk/bas[i-1]%(n/i+1);
msk-=k*bas[i-1];
k=gcd[k][x/i];
msk+=k*bas[i-1];
}
ret+=dfs1(x-1,msk);
mp[x][_]=ret;
return mp[x][_]=ret;
}
vector<int> dfs2(int x,int msk,int rem)
{
if(!x) return vector<int>();
if(x>m || msk/bas[x-1]%(n/x+1)!=1)
{
int tmp=mp[x-1][msk];
if(tmp>rem) return dfs2(x-1,msk,rem);
rem-=tmp;
}
for(int i=1;i<=m;i++)
{
if(x%i) continue;
int tmp=msk/bas[i-1]%(n/i+1);
msk-=tmp*bas[i-1];
tmp=gcd[tmp][x/i];
msk+=tmp*bas[i-1];
}
vector<int> ret=dfs2(x-1,msk,rem);
ret.push_back(x);
return ret;
}
void solve()
{
scanf("%lld",&k);
vector<int> ans=dfs2(n,0,k);
printf("%lld ",(int)ans.size());
for(int x:ans) printf("%lld ",x);
printf("\n");
}
signed main()
{
for(int i=0;i<=40;i++) for(int j=0;j<=40;j++) if(i|j) gcd[i][j]=__gcd(i,j);
for(int i=bas[0]=1;i<=m;i++) bas[i]=bas[i-1]*(n/i+1);
dfs1(n,0);
int t;cin>>t;
while(t--) solve();
}