题解 随

传送门

神仙题+细节题+手残 = 上午9点调到下午6点
自闭一整天快炸掉了
考场上没看到 \(\%mod\) 乱推了个式子 运气好骗了10pts

先证个结论:
题面里那个\(a*(b^{(10^9+5)})\),如此阴间的式子肯定不是乱给的
注意到答案可被表示为\(a/b\)的形式,
同时题面中想算\(a/b\)需要乘法和加法
那么我们可以猜个结论:所给式子与\(a/b\)相关,且同样满足可加性和可乘性
证一下:
\(\frac{a}{b}+\frac{a}{b} = \frac{2a}{b}\), 同时有\(a*(b^{(10^9+5)}) + a*(b^{(10^9+5)}) = 2a*(b^{(10^9+5)})\)
\(\frac{a}{b}*\frac{a}{b} = \frac{a^2}{b^2}\), 同时有\(a*(b^{(10^9+5)}) * a*(b^{(10^9+5)}) = a^2*(b^{2^{(10^9+5)}})\)
所以直接把这坨式子当分数用就好了

接下来是55pts做法:
考虑矩阵乘法,因为每个余数能向哪些余数转移是一定的,
即系数矩阵不变,直接快速幂就好
时间复杂度\(O(mod^3logm)\)

Code:

#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 100010
#define SIZE 300
#define ll long long 
#define ld long double
#define usd unsigned
#define ull unsigned long long
//#define int long long 

#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++)
char buf[1<<21], *p1=buf, *p2=buf;
inline int read() {
	int ans=0, f=1; char c=getchar();
	while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
	while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
	return ans*f;
}

int n, m, mod;
int a[N];
const ll k=1e9+5, mod2=1e9+7;

inline ll md(ll a) {return a>=mod2?a-mod2:a;}

struct matrix{
	int n, m;
	ll a[SIZE][SIZE];
	
	matrix() {memset(a, 0, sizeof(a));}
	matrix(int n_, int m_):n(n_),m(m_) {memset(a, 0, sizeof(a));}
	void resize(int n_, int m_) {n=n_; m=m_;}
	void put() {for (int i=1; i<=n; ++i) {for (int j=1; j<=m; ++j) cout<<a[i][j]<<' '; cout<<endl;}}
	inline ll* operator [] (int i) {return a[i];}
	inline matrix operator * (matrix b) {
		matrix ans(n, b.m);
		for (int i=1; i<=n; ++i) 
			for (int k=1; k<=m; ++k) 
				if (a[i][k]) for (int j=1; j<=b.m; ++j) 
					ans[i][j] = md(ans[i][j]+a[i][k]*b[k][j]%mod2);
		return ans;
	}
}dp, t;

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

matrix qpow(matrix a, ll b) {
	matrix ans=a; --b;
	while (b) {
		if (b&1) ans=ans*a;
		a=a*a; b>>=1;
	}
	return ans;
}

signed main()
{
	#ifdef DEBUG
	freopen("1.in", "r", stdin);
	#endif
	ll ans=0;
	
	n=read(); m=read(); mod=read();
	for (int i=1; i<=n; ++i) a[i]=read();
	
	const ll p=qpow(n, k);
	dp.resize(1, mod); t.resize(mod, mod);
	
	for (int i=0,t2; i<mod; ++i) 
		for (int j=1; j<=n; ++j) {
			t2 = i*a[j]%mod+1;
			t[i+1][t2] = md(t[i+1][t2]+p);
		}
	
	dp[1][2]=1;
	dp = dp*qpow(t, m);
	
	for (int i=1; i<=mod; ++i) ans=(ans+(i-1)*dp[1][i])%mod2;
	printf("%lld\n", ans);

	return 0;
}

然后是正解:
真不是人想的

前置技能:

先说原根的事吧:

质数P的原根rt满足1<=rt<P,且rt的1次方,2次方…(P-1)次方在模P意义下可以取遍1到(P-1)的所有整数.
显然,原根的1次方,2次方…(P-2)次方在模P意义下都不为1,只有(P-1)次方在模P意义下为1.这也是一个数成为原根的充分必要条件.

所以说令质数P的原根为rt, 则\([rt^1\%P, rt^{n-1}\%P]\)\([1, P-1]\)形成双射(?)关系
利用此性质,可以将\([0, \varphi (P)]\)内的乘法转换为\([0, \varphi (P)-1]\)范围内的加法

再说循环矩阵:
这部分谢谢战神帮我推了数个式子,并提供了这个链接
先盗张图:

性质(1): 用第一行就可以推出整个矩阵
性质(2): 循环矩阵乘法时间复杂度为\(O(n^2)\)

为了节省空间,既然由第一行就可以推出整个矩阵,那么我们就只存第一行好了
那这样怎么作乘呢?
考虑原式: \(C_{1,j} = \sum\limits^n_{k=1}a_{1,k}\times b_{k,j}\)
注意这个\(b_{k,j}\), 由循环关系,\(b_{k,j} = b_{1,(k+j-1)\%n}\)
\(b_{k,j}\)在第一行对应的元素为\(b_{1,t}\)
则可导出 \(t = (k+j-1)\%n\)
特别注意: 这里为了避免取模后t少个n,要写成\(t = (k+j-2)\%n+1\)的形式
至于为什么,这里留坑待填
综上,可推得关系:
\(C_{1,x} = \sum\limits_{(i+j-2)\%n+1=x}a_{1,i}*b_{1,j}\)
即可进行乘法.
学这个花了我半个下午

那么回到这到题:
mod \(\leq 1000\), 那矩阵乘法的复杂度要是能优化到\(O(n^2)\)就能过掉了
对着题解找思路就是这么流畅自然
那么考虑如何用循环矩阵处理本题
50pts的系数矩阵是由下标进行乘法取模推得,不满足循环
而若使转移时向下标相加取模的方向转移,则满足转移跨度相等,均为行下标
我在说什么,跳过跳过
而利用原根可以在这里用加法建立循环矩阵

blog写到这里被大佬dis了
这个矩阵仅仅是一个系数矩阵,因为是由加法构造,满足循环关系而已
我之前通过矩阵乘法意义找关系的思路是错误的,也因此没推出什么有用的结论
这个题的难点在于利用原根使得构造一个满足循环关系的系数矩阵成为可能
而不是矩阵乘的意义 stO @Yubai

这题卡细节比较多,过程中模数换了好几个
输出\(\%1e9+7\)
\(x*a_i \% mod\)
矩阵乘\(\% \varphi(mod)-1=mod-2\)
最后这个直接卡我一下午一个比我还执着的3真的很让人崩溃啊

Code:

#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 100010
#define ll long long 
#define ld long double
#define usd unsigned
#define ull unsigned long long
//#define int long long 

#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++)
char buf[1<<21], *p1=buf, *p2=buf;
inline int read() {
	int ans=0, f=1; char c=getchar();
	while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
	while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
	return ans*f;
}

ll n, m, mod;
ll a[N], table[N], power[N];
const ll k=1e9+5, mod2=1e9+7;

inline ll md(ll a) {return a>=mod2?a-mod2:a;}

struct matrix{
	int n;
	ll a[N];
	
	matrix() {memset(a, 0, sizeof(a));}
	matrix(int n_):n(n_) {memset(a, 0, sizeof(a));}
	void resize(int n_) {n=n_;}
	void put() {for (int i=1; i<=n; ++i) cout<<a[i]<<' '; cout<<endl;}
	inline ll& operator [] (int i) {return a[i];}
	inline matrix operator * (matrix b) {
		matrix ans(n);
		for (int i=1; i<=n; ++i) 
			for (int j=1; j<=n; ++j) 
				ans[(i+j-2)%(n-1)+1] = md(ans[(i+j-2)%(n-1)+1]+a[i]*b[j]%mod2); //, cout<<(i+j-2)%mod+1<<endl;
		return ans;
	}
}dp, t;

int getrt() {
	ll t;
	for (int i=1; i<mod; ++i) {
		t = 1;
		for (int j=1; j<mod-1; ++j) {
			t = t*i%mod;
			//cout<<i<<endl;
			if (t==1) goto jump;
		}
		
		power[0]=1;
		for (int j=1; j<mod-1; ++j) {
			power[j]=power[j-1]*i%mod;
			table[power[j]] = j;
		}
		//for (int si=1; i<=mod; ++i) cout<<power[i]<<' '; cout<<endl;
		//for (int i=1; i<=mod; ++i) cout<<table[i]<<' '; cout<<endl;
		return i;
		
		jump: ;
	}
	//cout<<"error"<<endl;
}

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

matrix qpow(matrix a, ll b) {
	matrix ans=a; --b;
	while (b) {
		if (b&1) ans=ans*a;
		a=a*a; b>>=1;
	}
	return ans;
}

signed main()
{
	#ifdef DEBUG
	freopen("1.in", "r", stdin);
	#endif
	ll ans=0;
	
	n=read(); m=read(); mod=read();
	for (int i=1; i<=n; ++i) a[i]=read();
	
	ll rt=getrt();
	//cout<<"rt: "<<rt<<endl;
	const ll p=qpow(n, k);
	dp.resize(mod);
	
	for (int i=1,t2; i<=n; ++i) {
		t2 = table[a[i]]+1;
		dp[t2] = md(dp[t2]+p); //, cout<<"add "<<t2<<' '<<dp[t2]<<endl;
	}
	
	//dp.put();
	dp = qpow(dp, m);
	//dp.put();
	
	ll tk=1;
	for (int i=1; i<=mod; ++i,tk=tk*rt%mod) ans=(ans+power[i-1]*dp[i]%mod2)%mod2;
	printf("%lld\n", ans);

	return 0;
}
posted @ 2021-06-08 19:09  Administrator-09  阅读(29)  评论(1编辑  收藏  举报