P3704 [SDOI2017]数字表格

神,不会低头!


首先,有 出现,说明这道题很阴间;其次,有 出现,说明这道题不可做;最后,多测和取模,说明这道题是给神仙做的。

结束。


开玩笑哒,贺肯定还是要贺的。

题目简介

题目来源: 所属不详(可能是集训题之类的)

评测链接 https://www.luogu.com.cn/problem/P3704
评测链接 https://loj.ac/p/2000

形式化题目:给定一个 的矩阵,权值 ,求 ,多测,模数

数据范围:

这显然看起来是一道莫比乌斯反演的题目,我们可以考虑从结果入手:

没有任何一个部分是我们能够快速处理的。很难受。

由于从古至今斐波那契都是人类智慧的一大难题,所以暂且不考虑,我们先解决 的问题,并想办法来让 变得像 一样好处理。我们考虑把 提出,通过枚举 的值来解决问题。

这里定义 ,化简答案:

容易发现这个 我们在之前也经常看见,所以我们可以先把 进行转化,让这个函数能够在 的时间内求出,想看推导过程的读者可以参照 ,这里给出结果:

然后我们把 带回式子中,得到一个较冗长但是时间复杂度更优的式子:

然后容易发现并不是很好从某一部分入手了,但此时的时间复杂度对于 多测数据高达 的题目而言还是太困难了(虽然已经有 了)。

由于有极其难搞的阴间多测,所以我们不用考虑在 上优化,所以考虑把 提出考虑。因为乘方的性质,我们可以把幂上的那个 给剔下来,变换一下顺序,设 ,有:

然后我们最后来解决 的问题,重设 表示为:

然后通过 来再次表示

容易发现指数部分可以通过数论分块在根号内结束,那如果我们可以预处理出 的前缀积,询问复杂度就在 ,其中 为数论分块, 为快速幂。

接下来的操作就在于,如何能在有限的时间复杂度内预处理出 的前缀积, 讲了枚举因数和枚举倍数两种方法,这里写出枚举倍数的方法:

我们用 来替代 得到:

这样的话,我们可以在 的时间内预处理出所需要的

最终我们得到了一个时间复杂度为 的解法,可以通过本题。

注意 std::memset 的用法,不要向我一样被卡了十多分钟才看出来。另外,根据欧拉定理(),我们的整个运算都是建立在模数为 的前提下的,所以涉及到指数的运算都要将模数变为 ,这是必要的。

AC Code
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// ----- Eternally question-----
// Problem: P3704 [SDOI2017]数字表格
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3704
// Memory Limit: 125 MB
// Time Limit: 2000 ms
// Written by: Eternity
// Time: 2023-01-03 20:42:24
// ----- Endless solution-------

#include<bits/stdc++.h>
#define re register
typedef long long ll;
template<class T>
inline void read(T &x)
{
x=0;
char ch=getchar(),t=0;
while(ch<'0'||ch>'9') t|=ch=='-',ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
if(t) x=-x;
}
template<class T,class ...T1>
inline void read(T &x,T1 &...x1){ read(x),read(x1...); }
template<class T>
inline void write(T x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
template<>
inline void write(bool x){ putchar(x?'1':'0'); }
template<>
inline void write(char c){ putchar(c); }
template<>
inline void write(char *s){ while(*s!='\0') putchar(*s++); }
template<>
inline void write(const char *s){ while(*s!='\0') putchar(*s++); }
template<class T,class ...T1>
inline void write(T x,T1 ...x1){ write(x),write(x1...); }
template<class T>
inline bool checkMax(T &x,T y){ return x<y?x=y,1:0; }
template<class T>
inline bool checkMin(T &x,T y){ return x>y?x=y,1:0; }
const int MAXN=1e6+10;
const ll Mod=1e9+7;
const int S=1e6;
int Test,N,M;
ll H[MAXN],Fib[MAXN];
ll Mu[MAXN],Pri[MAXN],Tot;
ll Inv_fib[MAXN],Mul_H[MAXN];
bool Is[MAXN];
inline ll qPow(ll a,ll b)
{
// if(b<0) b+=Mod;
ll res=1;
while(b)
{
if(b&1) res=res*a%Mod;
a=a*a%Mod;b>>=1;
}
return res;
}
inline void sieve(int n)
{
Mu[1]=1,Is[1]=1;
for(int i=2;i<=n;++i)
{
if(!Is[i]) Pri[++Tot]=i,Mu[i]=-1;
for(int j=1;j<=Tot&&i*Pri[j]<=n;++j)
{
Is[i*Pri[j]]=1;
if(i%Pri[j]==0)
{
Mu[i*Pri[j]]=0;
break;
}
Mu[i*Pri[j]]=-Mu[i];
}
}
}
inline void init()
{
sieve(S);
// printf("Test:%d\n",qPow(2,5));
Fib[0]=0,Fib[1]=Inv_fib[1]=1;
for(int i=2;i<=S;++i) Fib[i]=(Fib[i-1]+Fib[i-2])%Mod;
// for(int i=1;i<=10;++i) write(Fib[i],' ');puts("");
for(int i=2;i<=S;++i) Inv_fib[i]=qPow(Fib[i],Mod-2);
for(int i=1;i<=S;++i) H[i]=1ll;
for(int i=1;i<=S;++i)
{
if(!Mu[i]) continue;
for(int j=1;i*j<=S;++j)
H[i*j]=H[i*j]*(Mu[i]==1?Fib[j]:Inv_fib[j])%Mod;
}
Mul_H[0]=1;
for(int i=1;i<=S;++i) Mul_H[i]=Mul_H[i-1]*H[i]%Mod;
}
inline int getEd(int x,int n){ return n/(n/x); }
inline ll solve(int n,int m)
{
ll res=1;
int ed=std::min(n,m);
for(int l=1,r=0;l<=ed;l=r+1)
{
r=std::min(std::min(getEd(l,n),getEd(l,m)),ed);
ll btm=Mul_H[r]*qPow(Mul_H[l-1],Mod-2)%Mod;
res=res*qPow(btm,1ll*(n/l)*(m/l)%(Mod-1))%Mod;
}
return res;
}
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
read(Test);
init();
while(Test--)
{
read(N,M);
write(solve(N,M),'\n');
}
return 0;
}
/*

*/