Test 2022.09.23

今天不知道是什么专场

T1 数学作业

考场上没有想出来,看到\(10^{18}\)的数据立马以为是妥妥的结论题,实际上打了表发现并没有发现有什么规律,然后就转而想到一定是有个通项公式吧,所以早上两个小时考试一个半小时最后做了一个十分钟就打得出来的暴力(\(SB\)的我竟然还想着求逆元???)。

点击查看代码
#include<cstdio>
#include<iostream>
#include<algorithm>
#define int long long
using namespace std;
int n,m;
int power(int a,int b,int p)
{
	int ans=1;
	while(b)
	{
		if(b&1)
			ans=ans*a%p;
		a=a*a%p;
		b>>=1;
	}
	return ans;
}
int cnt(int x)
{
	int sum=0;
	while(x)
	{
		sum++;
		x/=10;
	}
	return power(10,sum,m);
}
bool isp(int x)
{
	if(x==1)return 0;
	int i;
	for(i=2;i*i<=x;i++)
		if(x%i==0)break;
	if(i*i<=x)return 0;
	return 1;
}
signed main()
{
	freopen("math.in","r",stdin);
	freopen("math.out","w",stdout);
	scanf("%lld%lld",&n,&m);
//	if(isp(m))
//	{
//		int inv9=power(9,m-2,m); 
//		long long x1=power(10,n+1,m),x2=(-10+m)%m;
//		long long tmp1=n/m+1,x3=(tmp1*m-n)%m;
//		long long ans=((x1+x2)*inv9%m+x3)%m;
//		long long ans=((power(10,n+1,m)-10+m)%m*inv9%m-n+m)%m;
//		ans=(ans%m*inv9)%m;
//		printf("%lld",ans);
//	}
//	else
//	{
		long long ans=0;
		for(int i=1;i<=n;i++)
			ans=((ans*cnt(i))%m+i)%m;
		printf("%lld",ans);
//	}
	return 0;
}
//int inv[100];
//void get_inv(long long n,long long p)
//{
//	inv[1]=1;
//	for(int i=2;i<=n;++i)
//		inv[i]=p-1ll*(p/i)*inv[p%i]%p,printf("%d\n",inv[i]);
//}

所以除了通项和规律,难道就没有办法通过这么大的数据了吗?肯定是有的!那就是矩阵乘法,只是本蒟蒻一直都和抗拒矩阵乘法(因为写不来!),详情见我第一篇博客为何要使用推通项的方法来做。所以今天一定要把矩阵乘法给拿捏了!
首先,矩阵乘法为什么快呢?因为矩阵乘法可以使用快速幂啊,通过结合律(是没有交换律的)再结合快速幂的思想,我们很容易就能在logN的时间内求出这个矩阵的N次方。
其中乘法的初值应该为单位矩阵(乘任意矩阵都是该矩阵)

矩阵的元素

首先是存矩阵的二维数组\(a[i][j]\)、其次是矩阵的行\(n\),列\(m\)

点击查看代码
struct Matrix{int a[maxn][maxn],n,m;Matrix{memset(a,0,sizeof a);};}

单位矩阵的构造方法

其实很简单,按照定义来赋值,然后再赋值行 列就行了

点击查看代码
Matrix base(int n)//构造一个n*n的矩阵
{
	Matrix tmp;tmp.m=tmp.n=n;
	for(int i=1;i<=n;i++)tmp.a[i][i]=1;
	return tmp;
}

接下来是最重要的矩阵乘法,模拟就行

点击查看代码
Matrix operator *(Matrix a,Matrix b)
{
	Matrix tmp;tmp.n=a.n,tmp.m=b.m;
	for(int i=1;i<=a.n;i++)
	{
		for(int j=1;j<=b.m;j++)
		{
			int c=0;
			for(int k=1;k<=a.m;k++)
				c+=a.a[i][k]*b.a[k][j];
			tmp.a[i][j]=c;
		}
	}
	return tmp;
}

最后就是矩阵快速幂了

点击查看代码
Matrix Mpower(Matrix a,int b)//前提是这个转移矩阵得是正方形的
{
	Matrix ans=base(a.n);
	while(b)
	{
		if(b&1)
			ans=ans*a//这个地方乘的顺序一定要注意,矩阵是没有交换律的
		a=a*a;
		b>>=1;
	}
	return ans;
}

好了,接下来就是这道题的转移矩阵了,写出转移矩阵和初始矩阵对于我们这种蒟蒻来说也是非常重要的,值得一提的是,我们现在拥有的递推方程是\(f[i]=f[i-1]*10^{cnt(i)}+i,cnt(i):i\)的位数。
那么我们就把这个递推式里面所有的元素写下来(假设我们当前已知右边所有的值):$$f[i-1],i,10^k$$注意到从这个式子的\(i\)到下个式子的\(i+1\),我们是需要加上一个常数的,所以我们还需要一个常数\(1\)来辅助,那么当前确定的就是

\[\begin{bmatrix}f[i-1] \\i \\1 \end{bmatrix} \]

要转移到

\[\begin{bmatrix}f[i] \\i+1 \\1 \end{bmatrix} \]

首先根据矩阵的性质和我们要转移的矩阵,我们写出的转移矩阵肯定是\(3*3\)
结合递推公式分析前者的行和后者的列的对应关系,就可以推出每一行的系数是多少
综合起来就是这样的:

\[\begin{bmatrix} 10^k&1 &0 \\ 0& 1 &1 \\ 0& 0 &1 \end{bmatrix} \]

这样再做快速幂就差不多了

点击查看代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define int long long
using namespace std;
const int maxn=15;
struct Matrix
{
	int a[12][12];int n,m;
	Matrix(){memset(a,0,sizeof a);}/*一定要初始化*/
}t[maxn*maxn];
int N,M;
Matrix operator *(Matrix a,Matrix b)
{
	Matrix tmp;
	for(int i=1;i<=a.n;i++)/*A的行数*/
		for(int j=1;j<=b.m;j++)/*B的列数*/
		{
			int c=0;
			for(int k=1;k<=b.n;k++)
				c=(c%M+(a.a[i][k]%M)*(b.a[k][j]%M)%M)%M;
			tmp.a[i][j]=c;
		}
	tmp.n=a.n,tmp.m=b.m;
	return tmp;
}
Matrix base(int n,int m)
{
	Matrix tmp;tmp.n=n,tmp.m=m;
	for(int i=1;i<=n;i++)tmp.a[i][i]=1;
	return tmp;
}
Matrix Mpower(Matrix a,int b)
{
	Matrix ans=base(3,3);
	while(b)
	{
		if(b&1)
			ans=ans*a;
		a=a*a;
		b>>=1;
	}
	return ans;
}
int power(int a,int b)
{
	int ans=1;
	while(b)
	{
		if(b&1)
			ans=ans*a;
		a=a*a;
		b>>=1;
	}
	return ans;
}
int cnt=0;
void form(int tar)/*18*/
{
	long long ch=1;
	for(int now=1;now<=tar;now++)
	{
		cnt++;ch*=10;
		t[cnt].a[1][1]=ch,t[cnt].a[1][2]=t[cnt].a[2][2]=t[cnt].a[2][3]=t[cnt].a[3][3]=1;
		t[cnt].n=t[cnt].m=3;
	}	
}
int num(int x)
{
	int sum=0;
	while(x)
	{
		sum++;
		x/=10;
	}
	return sum;
}
long long times[20];
void pre()
{
	times[1]=9;
	for(int i=2;i<=18;i++)times[i]=times[i-1]*10;
}
void print(Matrix a)
{
	for(int i=1;i<=a.n;i++)
	{
		for(int j=1;j<=a.m;j++)
			printf("%lld ",a.a[i][j]);
		printf("\n");
	}
}
signed main()
{
//	freopen("math.in","r",stdin);	
//	freopen("math.out","w",stdout);
	form(18);
	scanf("%lld%lld",&N,&M);
	Matrix ans;ans.a[1][1]=0,ans.a[2][1]=1,ans.a[3][1]=1,ans.n=3,ans.m=1; 
	pre();
	for(int i=1;i<num(N);i++)
		ans=Mpower(t[i],times[i])*ans;
	int tmp=N-power(10,num(N/10))+1;
	ans=Mpower(t[num(N)],tmp)*ans;
	printf("%lld",ans.a[1][1]%M);
	return 0;
}

T2、T3都是比较奇怪的题

反正我没做出来

T4 数三角形

一道数学题洛谷上面难度评级竟然是紫色,没想到我竟然能切掉,最近真的是人品爆棚了。

题意

给定一个\(n*m\)的方格,求其中三个顶点都在格点上的三角形的数量

分析

首先我想到的肯定是生物里面算遗传的时候用的方法,比如求一个后代只患一种病的概率,就用所有的减去不患病的概率和两种病都患的概率。这道题也是一样的,我们用所有的三角形数(不论是否合法)减去三点共线的三角形数,就是我们最后的答案了。总的方案数很明显就是\(\tbinom{(n+1)*(m+1)}{3}\),而横和竖重复的方案数也很简单,就是\((n+1)*\tbinom{m+1}{3}\)\((m+1)*\tbinom{n+1}{3}\)那么斜着的如何求呢?这一点我受到了机房同学的一点启发,他们在这道题上面想要求斜率来算,但我觉得斜率必定会带来浮点数,这样的话就会使计算变得复杂,所以怎么解决呢?其实斜率无非就是\(\Delta x\)\(\Delta y\)的比值,那我们为什么不枚举这两个整数呢?进而想到直接两层枚举横纵线段长度,因为这样就能保证所有斜着的共线情况都能够被计算,就是\(\sum_{i=1}^m\sum_{j=1}^{n}(m-i+1)*(n-j+1)(gcd(i,j)-1)\)
最后再用总的情况减去不合法就行了。

Code

点击查看代码
#include<cstdio>
#include<iostream>
#include<algorithm>
#define int long long 
using namespace std;
int C_3(int x){return x*(x-1)*(x-2)/6;}
int gcd(int x,int y){return x%y==0?y:gcd(y,x%y);}
int n,m;
signed main()
{
	scanf("%lld%lld",&n,&m);
//	double start=clock();
	long long ans1=C_3((n+1)*(m+1))-(n+1)*C_3(m+1)-(m+1)*C_3(n+1);
	long long ans2=0;
	for(int i=1;i<=m;i++)/*横长度*/
		for(int j=1;j<=n;j++)/*纵长度*/
			ans2+=(m-i+1)*(n-j+1)*(gcd(i,j)-1);
	ans2*=2;
	printf("%lld",ans1-ans2);
//	double end=clock();
//	cout<<endl<<end-start; 
	return 0;
}
posted @ 2022-09-23 21:56  Hanggoash  阅读(7)  评论(0编辑  收藏  举报
动态线条
动态线条end