• [ACNOI2022]做过也不会


    题目

    题目背景
    卷爷的刀已经抵在喉头;寒光一闪;我看到鲜血飞溅。我的身体不受控制地坠向地面……

    我从现实中惊醒。“幸好在梦世界里不会发生这样的事。” 我长吁一口气。

    题目描述
    n × n n\times n n×n 矩阵 A A A 的行列式,其中
    A i , j = { 1 ( i = j ) 0 ( i ≠ j ∧ i ∣ j ) C ( otherwise ) A_{i,j}=

    {1(i=j)0(ijij)C(otherwise)
    Ai,j=10C(i=j)(i=jij)(otherwise)

    998244353 998244353 998244353 取模。

    数据范围与提示
    n ⩽ 1 0 11 n\leqslant 10^{11} n1011 。时限 6 s \rm 6s 6s

    思路

    简单推导 det ⁡ \det det 的方式:下一行减去上一行,变成海森堡矩阵。

    复杂方法:将对角线上 1 1 1 视作 C + ( 1 − C ) C+(1-C) C+(1C),枚举哪些位置选择了 ( 1 − C ) (1-C) (1C),则剩余部分是主子式。

    引理:当且仅当主子式保留的行列编号 { x i } \{x_i\} {xi} 满足 x 1 ∣ x 2 ∣ x 3 ∣ ⋯ ∣ x k x_1\mid x_2\mid x_3\mid\cdots\mid x_k x1x2x3xk 时,其 d e t \rm det det C k C^k Ck,否则是 0 0 0

    证明:每一行都只有一个真后缀为全零,其余是 C C C 。所以必为下三角矩阵,此时两两之间有倍数关系。

    □ \square

    由此,我们对没选择的行列编号进行 d p \tt dp dp,可以很容易地写出转移
    f ( n ) = C ( 1 − C ) n − 1 + ∑ d ∣ n f ( d ) ( 1 − C ) n − d − 1 C f(n)=C(1-C)^{n-1}+\sum_{d\mid n}f(d)(1-C)^{n-d-1}C f(n)=C(1C)n1+dnf(d)(1C)nd1C

    以及
    a n s = ( 1 − C ) n + ∑ i = 1 n f ( i ) ( 1 − C ) n − i ans=(1-C)^n+\sum_{i=1}^{n}f(i)(1-C)^{n-i} ans=(1C)n+i=1nf(i)(1C)ni

    稍微变形一下
    f ( n ) ( 1 − C ) − n = C 1 − C + ∑ d ∣ n C 1 − C ( f ( d ) ( 1 − C ) − d ) f(n)(1-C)^{-n}=\frac{C}{1-C}+\sum_{d\mid n}\frac{C}{1-C}(f(d)(1-C)^{-d}) f(n)(1C)n=1CC+dn1CC(f(d)(1C)d)

    简记 v : = C 1 − C v:=\frac{C}{1-C} v:=1CC,不妨设 C ≠ 1 C\ne 1 C=1 。并记 g ( n ) : = f ( n ) ( 1 − C ) − n g(n):=f(n)(1-C)^{-n} g(n):=f(n)(1C)n
    g ( n ) = v + ∑ d ∣ n d ≠ n v ⋅ g ( d ) (1) g(n)=v+\sum_{d\mid n}^{d\ne n}v\cdot g(d)\tag{1} g(n)=v+dnd=nvg(d)(1)

    a n s = ( 1 − C ) n ∑ i = 1 n g ( i ) ans=(1-C)^n\sum_{i=1}^{n}g(i) ans=(1C)ni=1ng(i)

    注意到 g ( i ) g(i) g(i) 不是积性函数,我开始脑抽,打算写出关于质因子指数的 g ( i ) g(i) g(i) 的表达式。

    脑抽中

    n = ∏ p i t i n=\prod p_i^{t_i} n=piti,记
    λ l : = ∏ ( l − 1 + t i t i ) \lambda_l:=\prod{l-1+t_i\choose t_i} λl:=(til1+ti)

    即单个质因子的指数下降方案,在长为 l l l 的链上。容斥以确保每一步着实有下降。
    α l : = ∑ i = 0 l − 1 ( l − 1 i ) ( − 1 ) i λ l − i \alpha_l:=\sum_{i=0}^{l-1}{l-1\choose i}(-1)^i\lambda_{l-i} αl:=i=0l1(il1)(1)iλli

    g ( n ) = ∑ i = 1 log ⁡ n α i v i = ∑ i = 1 log ⁡ n v i ∑ j = 0 i − 1 ( i − 1 j ) ( − 1 ) j λ i − j = ∑ i = 1 log ⁡ n v i ∑ j = 0 i − 1 ( i − 1 j ) ( − 1 ) j ∏ ( i − j − 1 + t k t k ) = ∑ l ( ∑ i = l + 1 log ⁡ n v i ( i − 1 i − l − 1 ) ( − 1 ) i − l − 1 ) ∏ ( l + t k t k ) = ∑ l β l ⋅ I l + 1 ( n )

    g(n)=i=1lognαivi=i=1lognvij=0i1(i1j)(1)jλij=i=1lognvij=0i1(i1j)(1)j(ij1+tktk)=l(i=l+1lognvi(i1il1)(1)il1)(l+tktk)=lβlIl+1(n)
    g(n)=i=1lognαivi=i=1lognvij=0i1(ji1)(1)jλij=i=1lognvij=0i1(ji1)(1)j(tkij1+tk)=l(i=l+1lognvi(il1i1)(1)il1)(tkl+tk)=lβlIl+1(n)

    I l + 1 I^{l+1} Il+1 ( l + 1 ) (l{+}1) (l+1) I I I 的狄利克雷卷积的结果。于是可以 O ( log ⁡ n ) \mathcal O(\log n) O(logn) 次筛法解决,比如杜教筛或 min25 \texttt{min25} min25

    正解

    杜教筛不基于积性函数。而且,“因子的值求和” 不像狄利克雷卷积吗?这不像分治 FTT \textit{FTT} FTT 吗?

    事实上, ( 1 ) (1) (1) 式两边同时求前缀和立刻有
    ⇒ S ( n ) = v n + ∑ i = 2 n v ⋅ S ( ⌊ n / i ⌋ ) \Rightarrow S(n)=vn+\sum_{i=2}^{n}v\cdot S(\lfloor n/i\rfloor) S(n)=vn+i=2nvS(n/i)

    其中 S ( n ) = ∑ i ⩽ n g ( i ) S(n)=\sum_{i\leqslant n}g(i) S(n)=ing(i) 。于是杜教筛吧。时间复杂度 O ( n 2 / 3 ) \mathcal O(n^{2/3}) O(n2/3) 的……吗?

    注意非积性函数不能做严格意义上的线性筛。需优化。事实上,质因数的指数的可重集相同时, g g g 的值显然相同。因此我们只有很少的值需要枚举因子。但是怎么找到对应关系呢?

    一个方法是 hash \texttt{hash} hash 。当然我们也可以魔改线性筛,每个数记录其最大质因子和次数,并预处理每个质数的幂。然后存 f i f_i fi 为,与 i i i 拥有相同的指数可重集的数中,最小的一个。直接求 f i f_i fi 麻烦,可以求 j j j 使得 f i = f j f_i=f_j fi=fj 。设 i i i 移除最大质因子后得到 u u u,若 f u ≠ u f_u\ne u fu=u j = i u f u j=\frac{i}{u}f_u j=uifu,否则再做指数与质因数编号的判断。

    最终只有 500 + 500+ 500+ 个数需暴力计算,所以复杂度总算是 O ( n 2 / 3 ) \mathcal O(n^{2/3}) O(n2/3) 了。

    代码

    #include <cstdio>
    #include <algorithm> // Almighty XJX yyds!!
    #include <cstring> // oracle: ZXY yydBUS!!!
    #include <cctype> // I hate rainybunny.
    using llong = long long;
    # define rep(i,a,b) for(int i=(a); i<=(b); ++i)
    # define drep(i,a,b) for(int i=(a); i>=(b); --i)
    # define rep0(i,a,b) for(int i=(a); i!=(b); ++i)
    inline int readint(){
    	int a = 0, c = getchar(), f = 1;
    	for(; !isdigit(c); c=getchar()) if(c == '-') f = -f;
    	for(; isdigit(c); c=getchar()) a = a*10+(c^48);
    	return a*f;
    }
    
    const int LOGN = 40, MOD = 998244353;
    inline llong qkpow(llong b, int q){
    	llong a = 1;
    	for(; q; q>>=1,b=b*b%MOD) if(q&1) a = a*b%MOD;
    	return a;
    }
    
    const int MAXN = 23544347;
    bool isPrime[MAXN]; int* ppow[MAXN];
    int primes[MAXN], primes_size, g[MAXN];
    int fa[MAXN], big[MAXN], num[MAXN], nxt[MAXN], cnt[MAXN];
    void dfs(int &res, int x, int y){
    	if(x == 1){ if(g+y != &res){ res = (res+g[y])%MOD; } return; }
    	rep(i,0,num[x]) dfs(res,nxt[x],y), y *= big[x];
    }
    void sieve(const int &v, int n = MAXN-1){
    	memset(isPrime+2, true, n-1);
    	fa[1] = 1, g[1] = v;
    	for(int i=2,&len=primes_size; i<=n; ++i){
    		if(isPrime[i]){
    			primes[++len] = big[i] = i, cnt[i] = 1;
    			num[i] = 1, fa[i] = 2, nxt[i] = 1;
    			g[i] = int(llong(v+1)*v%MOD);
    			for(int j=2,p=i; true; ++j,p*=i)
    				if(p > n/i){ ppow[i] = new int[j]; break; }
    			for(int j=0,p=1; p<=n/i; ++j,p*=i,ppow[i][j]=p);
    		}
    		else{
    			const int rt = fa[nxt[i]];
    			if(rt != nxt[i]) g[i] = g[fa[i] = fa[rt*(i/nxt[i])]];
    			else if(big[i] != primes[cnt[rt]+1]){ // not nearest
    				fa[i] = rt*ppow[primes[cnt[rt]+1]][num[i]];
    				fa[i] = fa[fa[i]], g[i] = g[fa[i]];
    			}
    			else if(rt != 1 && num[rt] < num[i]){
    				fa[i] = nxt[rt]*ppow[big[rt]][num[i]]
    					*ppow[big[i]][num[rt]]; // switch
    				fa[i] = fa[fa[i]], g[i] = g[fa[i]];
    			}
    			else{ // I am smallest
    				static int wxk = 0; ++ wxk;
    				fa[i] = i, dfs(g[i],i,1);
    				g[i] = int(llong(g[i]+1)*v%MOD);
    			}
    		}
    		for(int j=1; j<=len; ++j){
    			const int to = i*primes[j]; if(to > n) break;
    			isPrime[to] = false, big[to] = big[i];
    			if(!(i%primes[j])){
    				if(nxt[i] == 1) num[to] = num[i]+1, nxt[to] = 1;
    				else num[to] = num[i], nxt[to] = nxt[i]*primes[j];
    				cnt[to] = cnt[i]; break;
    			}
    			num[to] = num[i], nxt[to] = nxt[i]*primes[j], cnt[to] = cnt[i]+1;
    		}
    	}
    }
    
    int res[4650];
    int getSum(const llong &n, const int &v, llong x){
    	if(x < MAXN) return g[x];
    	int &w = res[n/x]; if(~w) return w;
    	w = int(x%MOD); // easiest
    	for(llong l=2,r,val; l!=x+1; l=r){
    		val = x/l, r = x/val+1; // move
    		w = int((w+(r-l)%MOD*getSum(n,v,val))%MOD);
    	}
    	return w = int(llong(w)*v%MOD);
    }
    
    int main(){
    	llong n; scanf("%lld",&n); int c = readint();
    	if(c == 1){ puts(n <= 2 ? "1" : "0"); return 0; }
    	int v = int(c*qkpow(1-c+MOD,MOD-2)%MOD);
    	sieve(v); rep0(i,2,MAXN) g[i] = (g[i]+g[i-1])%MOD;
    	memset(res, -1, sizeof(res));
    	int ans = getSum(n,v,n)+1;
    	ans = int(ans*qkpow(1+MOD-c,int(n%(MOD-1)))%MOD);
    	printf("%d\n",ans);
    	return 0;
    }
    
    • 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
  • 相关阅读:
    基于Nodejs和mysql的工具市场客户信息管理系统
    攻防世界-very-easy-sql
    python之对接有道翻译API接口实现批量翻译
    Dockerfile文件解释
    [附源码]计算机毕业设计springboot新能源汽车租赁
    .net core 读取配置的几种方式
    SpringBoot整合RabbitMQ实战附加死信交换机
    如何看懂SparkUI?
    【华为上机真题 2022】停车场车辆统计
    IntelliJ IDEA 下 JavaWeb 配置MySQL 连接
  • 原文地址:https://blog.csdn.net/qq_42101694/article/details/125434341