Loading

随($rand$)

\(link\)
首先拿到这道题,先被他的这手分数虎住了。
然后回想起我做的\(minimax\)
想起来一般出题人并不会故意难为你而搞成这种分数,一般都是为了简化计算的。
所以我也懒得推式子,直接写了一个转化函数,测试了一下加法乘法在他给出的意义下成不成立。
测完不出所料,很完美,所以不用担忧这个分数问题了。

inline int tr( int a , int b ){ return T( a , qpow( b , P , P + 2 ) ) ; }
//	printf( "%ld %ld %ld\n" , tr( 1 , 5 )  , tr( 2 , 7 ) , tr( 17 , 35 ) ) ;

虽然很无脑,但这并不影响他是一个不错的方法。

然后才开始正式看题,起初并没有什么思路,看了一下数据范围,\(m<=1e9\),这是人都想带个\(log\)把它解决了。
然后我突发奇想手动模了一个样例,把\(m\)的过程搞出来,发现每次的系数都是一样的。
我就想了想矩阵,并觉得快速幂能解决。

当时没算明白复杂度,以为至少\(80pts\),所以花了很长时间调
调完之后跑\(50pts\)的数据,\(5s\)多,人直接傻了
不过最后脸比较白,常熟比较小,狗过\(50pts\),复杂度\(O(mod^3 * log(m))\)

50pts


#include <cstdio>
#include <cstring>
#include <algorithm>
#define R register int
#define printf Ruusupuu = printf
#define int long 

using namespace std ;
typedef long long L ;
typedef long double D ;
typedef unsigned long long G ;
const int N = 1e3 + 10 ;
const int P = 1e9 + 5 ;

int n , m , M , Ruusupuu ;

inline int read(){
	int w = 0 ; bool fg = 0 ; char ch = getchar() ;
	while( ch < '0' || ch > '9' ) ch |= ( ch == '-' ) , ch = getchar() ;
	while( ch >= '0' && ch <= '9' ) w = ( w << 1 ) + ( w << 3 ) + ( ch - '0' ) , ch = getchar() ;
	return fg ? -w : w ;
}

inline int qpow( int a , int b , int p ){
	int ans = 1 ;
	while( b ){
		if( b & 1 ) ans = 1ll * ans * a % p ;
		a = 1ll * a * a % p ; b >>= 1 ;
 	}	return ans ;
}

inline int T( int a , int b ){ return 1ll * a * b % ( P + 2 ) ; }
inline int Tt( int a , int b ){ return 1ll * a * b % M ; }
inline int Jj( int a , int b ){ return ( a + b > M ) ? ( a + b - M ) : ( a + b ) ; }
inline int J( int a , int b ){ return ( a + b > P + 2 ) ? ( a + b - P - 2 ) : ( a + b ) ; }
inline int tr( int a , int b ){ return T( a , qpow( b , P , P + 2 ) ) ; }

int a [N] , x , b [N][N] ; 

struct Mat{
	int m [N][N] ;
	int l , w ;
} A , B , an , ans ;

void sc(){
//	printf( "%ld\n" , tr( 3 , 2 ) ) ;
	n = read() , m = read() , M = read() ;
	for( R i = 1 ; i <= n ; i ++ ) x = read() , a [x] ++ ;
	for( R i = 1 ; i <= M ; i ++ ) A.m [1][i] = tr( a [i - 1] , n ) ; A.l = M , A.w = 1 ;
	for( R i = 1 ; i <= M ; i ++ ){
		for( R j = 1 ; j <= M ; j ++ ){
			int t = Tt ( i - 1 , j - 1 ) ;
			B.m [i][t + 1] = J ( B.m [i][t + 1] , A.m [1][j] ) ; 
		}
	} B.w = B.l = M ;
}

Mat mul( Mat a , Mat b ){
	Mat ans ; 
//	puts( "shit" ) ;
	for( R i = 1 ; i <= a.l ; i ++ )
	for( R j = 1 ; j <= a.l ; j ++ )
		ans.m [i][j] = 0 ;
	
	for( R i = 1 ; i <= a.w ; i ++ )
	for( R j = 1 ; j <= b.l ; j ++ )
	for( R k = 1 ; k <= a.l ; k ++ )
	ans.m [i][j] = J ( ans.m [i][j] , T ( a.m [i][k] , b.m [k][j] ) ) ;
	
	ans.l = a.l ;
	ans.w = a.w ; 
	return ans ;
} 

Mat qpow( Mat a , int b ){
	Mat ans ; ans.l = ans.w = a.l ;
	for( R i = 1 ; i <= a.l ; i ++ )
	for( R j = 1 ; j <= a.l ; j ++ ){
		if( i == j ) ans.m [i][j] = 1 ;
		else ans.m [i][j] = 0 ;
	}
	
	while( b ){
		if( b & 1 ) ans = mul( ans , a ) ;
		a = mul( a , a ) ;
		b >>= 1 ;  
	}
	
	return ans ;
}

void work(){
	an = mul( A , qpow( B , m - 1 ) ) ;	
 	int sum = 0 ;
 	for( R i = 1 ; i <= M ; i ++ ) sum = J( sum , T ( an.m [1][i] , ( i - 1 ) ) ) ;
 	printf( "%ld\n" , sum ) ;
}

signed main(){
	sc() ;
	work() ;
	return 0 ;
}

正解:原根转化循环矩阵优化矩阵乘法(折磨神的转化,先\(\%\)学长)
我们知道,\(O(mod^3 * log(m))\)是不行的,但是,有些东西可以确定。

  1. 每次转移的系数确实相同
  1. 正解\(m\)肯定需要带一个\(log\)

那唯一能优化的,只有矩阵乘法了。
我们在构建系数矩阵\(B\)的时候,是通过乘法构建的,这导致我们的系数矩阵平平无奇,只能\(mod^3\)暴力算
如果我们可以把乘法转化为加法,由于加法的特性\(a+b=(a-1)+(b+1)\),(乘法并不满足\(a*b=(a-1)*(b+1)\))导致我们构造出来的矩阵,下一行必定是上一行向右移一个单位的到的。
这样,在计算矩阵乘法的时候,我们只需要计算一行,就可以通过移动得到整个矩阵。
于是,矩阵乘法就变成\(n^2\),问题就可以解决了。

如何转化乘法为加法?

这个属实是因为我太菜了,没有看懂题目给的信息。
正确的回答就是原根。
他的作用是将\([1,\varphi(p)]\)\(mod \ \ p\)的乘法,转化为\([0,\varphi(p)-1]\)\(mod \ \ \varphi(p)\)的加法。
原理是欧拉定理\((x^a*x^b) \ mod \ p ==(x^{(a+b )\ mod \varphi(p)})mod p\)
貌似有很快的方法求原根,不过暴力貌似问题不大,因为一个数的所有原根对于我们的转化是等效的,所以求最小的就行了。
复杂度超不过\(( ^4\sqrt n)^2\).

之后就没什么难的了,建立一个映射数组,建系数的时候转过去,统计答案的时候转回来就行了
\(ps:\)我的循环矩阵乘法并不是主流写法,其实可以一行搞定,不过还要推一些东西,我们懒得推,所以有二倍的常数。

code


#include <cstdio>
#include <cstring>
#include <algorithm>
#define R register int
#define printf Ruusupuu = printf
#define int long 

using namespace std ;
typedef long long L ;
typedef long double D ;
typedef unsigned long long G ;
const int N = 1e3 + 10 ;
const int P = 1e9 + 5 ;

int n , m , M , Ruusupuu ;

inline int read(){
	int w = 0 ; bool fg = 0 ; char ch = getchar() ;
	while( ch < '0' || ch > '9' ) ch |= ( ch == '-' ) , ch = getchar() ;
	while( ch >= '0' && ch <= '9' ) w = ( w << 1 ) + ( w << 3 ) + ( ch - '0' ) , ch = getchar() ;
	return fg ? -w : w ;
}

inline int qpow( int a , int b , int p ){
	int ans = 1 ;
	while( b ){
		if( b & 1 ) ans = 1ll * ans * a % p ;
		a = 1ll * a * a % p ; b >>= 1 ;
 	}	return ans ;
}

inline int T( int a , int b ){ return 1ll * a * b % ( P + 2 ) ; }
inline int Tt( int a , int b ){ return 1ll * a * b % M ; }
inline int Jj( int a , int b ){ return ( a + b ) % ( M - 1 ) ; }
inline int J( int a , int b ){ return ( a + b > P + 2 ) ? ( a + b - P - 2 ) : ( a + b ) ; }
inline int tr( int a , int b ){ return T( a , qpow( b , P , P + 2 ) ) ; }

int a [N] , x , b [N][N] , rt , rl1 [N] , rl2 [N] ;  // rl1 -> 原根i -> rt ^ i , rl2 -> rt^i -> i ;  
bool fg [N] ;

struct Mat{
	int m [N][N] ;
	int l , w ;
} A , B , an , ans ;

void sc(){
//	printf( "%ld\n" , tr( 3 , 2 ) ) ;
	n = read() , m = read() , M = read() ;
	
	for( R i = 1 ; i < M ; i ++ ){
		memset( fg , 0 , sizeof( fg ) ) ;
		for( R j = 1 ; j < M ; j ++ ){
			int t = qpow( i , j , M ) ;
			if( fg [t] ) break ;
			else fg [t] = 1 ;
			if( j == M - 1 && t == 1 ) rt = i ;  
		} if( rt ) break ; 
	} //printf( "%ld\n" , rt ) ;
	
	for( R i = 0 ; i < M - 1 ; i ++ ){
		int t = qpow( rt , i , M ) ;
		rl1 [i] = t ; rl2 [t] = i ;
	}
	for( R i = 1 ; i <= n ; i ++ ) x = read() , a [rl2 [x]] ++ ;
	for( R i = 0 ; i < M - 1 ; i ++ ) A.m [0][i] = tr( a [i] , n ) ; A.l = M - 1 , A.w = 1 ;
	for( R i = 0 ; i < M - 1 ; i ++ ){
		for( R j = 0 ; j < M - 1 ; j ++ ){
			int t = Jj ( i , j ) ;
			B.m [i][t] = J ( B.m [i][t] , A.m [0][j] ) ; 
		}
	} B.w = B.l = M - 1 ;
}

Mat mul( Mat a , Mat b ){
	Mat ans ; 
//	puts( "shit" ) ;
	for( R i = 0 ; i < a.w ; i ++ )
	for( R j = 0 ; j < a.l ; j ++ )
		ans.m [i][j] = 0 ;
	
	for( R i = 0 ; i < a.l ; i ++ )
		for( R j = 0 ; j < b.w ; j ++ )
			ans.m [0][i] = J( ans.m [0][i] , T ( a.m [0][j] , b.m [j][i] ) ) ;//, printf( "%ld %ld\n" , a.m [1][j] , b.m [j][i] ) ; 
	for( R i = 1 ; i < a.w ; i ++ ){
		for( R j = 0 ; j < a.l ; j ++ )
			if( j ) ans.m [i][j] = ans.m [i - 1][j - 1] ;
		ans.m [i][0] = ans.m [i - 1][a.l - 1] ;
	}
	
 	ans.l = a.l ;
	ans.w = a.w ; 
	return ans ;
} 

Mat qpow( Mat a , int b ){
	Mat ans ; ans.l = ans.w = a.l ;
	for( R i = 0 ; i < a.l ; i ++ )
	for( R j = 0 ; j < a.l ; j ++ ){
		if( i == j ) ans.m [i][j] = 1 ;
		else ans.m [i][j] = 0 ;
	}
	
	while( b ){
		if( b & 1 ) ans = mul( ans , a ) ;
		a = mul( a , a ) ;
		b >>= 1 ;  
	}
	
	return ans ;
}

void work(){

	an = qpow( B , m - 1 ) ;

	an = mul( A , an ) ;	

 	int sum = 0 ;
 	for( R i = 0 ; i < M - 1 ; i ++ ) sum = J( sum , T ( an.m [0][i] , rl1 [i] ) ) ;
 	printf( "%ld\n" , sum ) ;
}

signed main(){
	sc() ; 
	work() ;
	return 0 ;
}

posted @ 2021-06-08 15:49  Soresen  阅读(98)  评论(0)    收藏  举报