「余姚中学 2019 联测 Day 4」随机除法

「余姚中学 2019 联测 Day 4」随机除法

好题,难就难在转移的高位前缀和

首先是一个浅显的 \(\text{dp}\) 状态,令 \(n=\Pi prime_i^{c_i}\)

则状态只跟 \(\{c_i\}\) 有关,这是一个可重集合,强制定义 \(c_i\ge c_{i-1}\) 最小表示出所有不同状态

搜索一下 \(\text{dp}\) 状态,发现只有 \(170000\) 左右的状态数

直接枚举因数转移复杂度显然是升天的,直接枚举子集状态转移复杂度也很高,并且不好确定系数

所以用一个高位前缀和处理优化枚举因数的转移

再说一遍,是高位前缀和枚举因数的转移,不同的因数可能对应同一个状态

常见的高位前缀和是 \(dp_{i,S}=dp_{i-1,S}+dp_{i,S-\{S_i\}}\)

转移具有单调性,对于状态排序之后,定义辅助数组 \(dp_{i,j}\) 表示对于 \(i\) 这个状态

它的子集(注意这个子集是未排序的)中和它不同的最低位置 \(\ge j\) 的总和

计算高位前缀和时,每次转移只会对于一个位置改变

枚举状态时,取得位置是 \(j\) ,调用时需要排序

而排完序之后 \(j\) 可能会后移,所以需要定义成 \(\ge j\) 的,否则会算多

比如转移 \((1,1,1)\leftarrow (0,1,1),(1,0,1),(1,1,0)\)

如果定义成 \(\le j\) 的状态,三种状态转移之后都变成 \((1,1,0)\)

原先在这个状态里的三个位置编号是 \((0,1,2)\)

如果都去 \((1,1,0)\) 这个状态里转移过来,原先 \((0,1,2)\) 对应的下标位置改变,变成

\((1,2,0)\)

\((0,2,1)\)

\((0,1,2)\)

我们访问的时候访问的应该是子状态中不同位置 \(\le j\) 的总和

而从这个下标改变的状态里转移过来时,原先 \(>j\) 的下标被移移动进 \(\le j\) 的范围

再转移就错了

所以正确定义状态之后就可以高位前缀和了

存储和访问状态可以用 \(\text{Hash,Trie,int128}\) 三种方法存储, \(\text{int128}\) 真香啊

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef __int128 Node;
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
#define Mod1(x) ((x>=P)&&(x-=P))

const int N=180000,P=1e9+7;

ll qpow(ll x,ll k=P-2) {
ll res=1;
for(;k;k>>=1,x=x*x%P) if(k&1) res=res*x%P;
return res;
}
Node Max,st[N];
int m,a[100],cnt,dp[N][20],F[N],pri[]={0,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79};
char str[30];

int Find(Node s){ return lower_bound(st+1,st+cnt+1,s)-st; }
void Load(Node s){
rep(i,1,20) a[i]=0;
for(int i=1;i<=20 && s>1; ++i) while(s%pri[i]==0) s=s/pri[i],a[i]++;
}
void dfs(int p,int lst,Node s) {
st[++cnt]=s;
rep(i,1,lst) {
if((s*=pri[p])>Max) return;
dfs(p+1,i,s);
}
}


int main(){
freopen("div.in","r",stdin),freopen("div.out","w",stdout);
Max=1e12,Max=Max*Max;
dfs(1,100,1);
fprintf(stderr,"States Number = %d\n",cnt);
sort(st+1,st+cnt+1);
rep(i,2,cnt) {
Node now; Load(now=st[i]);
int c=1;
rep(i,1,18) c=1ll*c*(a[i]+1)%P;
drep(j,18,0) {
if(!a[j]) dp[i][j]=dp[i][j+1];
else {
int k=j;
while(k>1 && a[k-1]==a[k]) --k;
int p=Find(now/pri[j]);
drep(d,j,k) {
dp[i][d]=dp[i][d+1]+dp[p][d];
Mod1(dp[i][d]);
}
j=k;
}
}
F[i]=(dp[i][1]+c)*qpow(c-1)%P;
rep(j,1,18) dp[i][j]+=F[i],Mod1(dp[i][j]);
}
while(~scanf("%s%d",str,&m)) {
Node n=0;
for(int i=0;str[i];++i) n=n*10+(str[i]-'0');
rep(i,1,m) {
int x;scanf("%d",&x); a[i]=0;
while(n%x==0) n=n/x,a[i]++;
}
sort(a+1,a+m+1,greater <int> ());
n=1;
rep(i,1,m) rep(j,1,a[i]) n=n*pri[i];
printf("%d\n",F[Find(n)]);
}
}