抗压题解解题压抗1

这篇博客详细分析了几道算法竞赛题目,包括POI2001和HAOI2007的反素数问题,利用搜索和性质判断反素数。接着讨论了五指山问题,通过裴蜀定理解决距离最短路径。还介绍了Matrix Power Series,使用矩阵快速幂解决矩阵加法序列。最后,探讨了曹冲养猪问题和Goldbach's Conjecture,涉及中国剩余定理和扩展欧几里得算法在解决同余方程组的应用。

这波就不打题号了

[POI2001][HAOI2007]反素数

https://www.luogu.com.cn/problem/P1463icon-default.png?t=L892https://www.luogu.com.cn/problem/P1463     

        题解:

        开始拿到这道题的时候毫无头绪,然而旁边两人都秒了,这波直接心态炸掉,无奈自己自己没做过TAT,好吧,其实这道题是可以打表的,而且并不是很慢

        打表程序:

#include<cstdio>
#include<algorithm>

using namespace std;

int vis[1000100],prime[1000100];
int cnt;

inline long long getsum(long long x)
{
	long long now,cn,ret=1;
	for(int i=1;i<=13;i++){
		now=x;
		cn=0;
		while(now%prime[i]==0){
			cn++;
			now/=prime[i];
		}
		ret*=(cn+1);
	}
	return ret;
}

int main(void)
{
	for(int i=2;i<=1000010;i++){
		if(vis[i]==0){
			vis[i]=i;
			prime[++cnt]=i;
		}
		for(int j=1;j<=cnt && i*prime[j]<=1000010;j++){
			if(vis[i*prime[j]]<prime[j] && vis[i*prime[j]]!=0)break;
			vis[i*prime[j]]=prime[j];
		}
	}
	long long maxn=0;
	for(long long i=1;i<=2000000000;i++){
		long long kk=getsum(i);
		if(maxn<kk)printf("%lld,",i);
		maxn=max(maxn,kk);
	}
}

        打表标程:

#include<cstdio>

using namespace std;

int ans[501]={0,1,2,4,6,12,24,36,48,60,120,180,240,360,720,840,1260,1680,2520,5040,7560,10080,15120,20160,25200,27720,45360,50400,55440,83160,110880,166320,221760,277200,332640,498960,554400,665280,720720,1081080,1441440,2162160,2882880,3603600,4324320,6486480,7207200,8648640,10810800,14414400,17297280,21621600,32432400,36756720,43243200,61261200,73513440,110270160,122522400,147026880,183783600,245044800,294053760,367567200,551350800,698377680,735134400,1102701600,1396755360,2001000000};
int n;

int main(void)
{
	scanf("%d",&n);
	for(int i=1;i<=500;i++){
		if(ans[i]>n){
			printf("%d",ans[i-1]);
			return 0;
		}
	}
}

        好吧蒙混过关的方法讲了,该说正经点的了

        关于正解:

        

        设\large x=\prod{p_i}^{k_i},则显然\large g(x)=\prod(k_i+1)

\large p_i严格递增,并且\large k_i=0也算在内,则如果\large k_x<k_y​并且\large x<y,那么显然这个数不可能是反素数,因为交换\large k_x,k_y​会更好,这样以来,交换前乘积和交换后乘积的因数个数相同,但是前者的积比后者的积大,所以相比之下后者会更加优秀。

        所以对于一个反素数,当它的\large p_i是单调上升的时候,其\large k_i必为单调递减。

        知道了这个性质之后这道题就要好做多了,咱们直接上搜索。

        思路的主旨就是在约数个数相同的情况下尽可能地让当前数小

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n;
int p[20]={0,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,51};
long long maxn=-1,num=-1;
void get(long long m,int f,int t,int pr)
{//f为当前质数的编号 ,当前指数<pr
//t为当前约数的个数 
    if(t>maxn||(t==maxn&&m<num))
        num=m,maxn=t;
    int j=0,nt;
    long long i=m;
    while(j<pr)
    {
        j++;
        if(n/i<p[f])break;
        nt=t*(j+1);
        i=i*p[f];
        if(i<=n)    get(i,f+1,nt,j);
    }
}
int main()
{
    cin>>n;
    get(1,1,1,30);
    cout<<num;
}

五指山 

        题目描述

        大圣在佛祖的手掌中。

        我们假设佛祖的手掌是一个圆圈,圆圈的长为 n,逆时针记为:0,1,2,⋯,n−1,而大圣每次飞的距离为 d。现在大圣所在的位置记为 x,而大圣想去的地方在 y。要你告诉大圣至少要飞多少次才能到达目的地。

        输入

        有多组测试数据。

        第一行是一个正整数 T,表示测试数据的组数;
每组测试数据包括一行,四个非负整数,分别为如来手掌圆圈的长度 n,筋斗所能飞的距离 d,大圣的初始位置 x 和大圣想去的地方 y。

注意孙悟空的筋斗云只沿着逆时针方向翻。

        输出

        对于每组测试数据,输出一行,给出大圣最少要翻多少个筋斗云才能到达目的地。如果无论翻多少个筋斗云也不能到达,输出 Impossible。

        样例输入

        2
        3 2 0 2
        3 2 0 1

        样例输出

        1
        2

        提示

        【数据范围与提示】
        对于全部数据,2<n<10^9 ,0<d<n,0≤x,y<n。

        题解:

        这道题错的原因是自己对于exgcd 的运用能力相当的差!所以有必要好好复习

        我们知道:

        裴蜀定理:对于对于给定的正整数a,b,存在x,y,满足 \large ax+by=gcd(a,b)

        换句话说:方程a*x+b*y=c有解的充要条件为c是gcd(a,b)的整数倍

        知道了这个定理之后,我们就可以用拓展欧几里得算法,对于给定的a和b,求出x,y

inline int exgcd(int a,int b,int &x,int &y)
{
    if(b==0){x=1,y=0;return a;}
    int d=exgcd(b,a%b,x,y);
    int z=x;
    x=y;
    y=z-(a/b)*y;
    return d;
} 

         求出x,y又有什么用呢?我们要求解的是\large ax\equiv c (mod \ b )

        \large ax\equiv c (mod \ b )等价于\large ax+by=c

        \large ax+by=gcd(a,b)和 \large ax+by=c是不是很像,解出来的x,y再乘上一个\large \frac{c}{gcd(a,b)}就是解了

#include<cstdio>

using namespace std;

inline long long exgcd(long long a,long long b,long long &xx,long long &yy)
{
	if(b==0){xx=1,yy=0;return a;}
	long long dd=exgcd(b,a%b,xx,yy);
	long long z=xx;
	xx=yy;
	yy=z-(a/b)*yy;
	return dd;
}

int T;
long long n,d,x,y;
long long ans;

inline long long qpow(long long xx,long long yy)
{
	long long ret=1;
	while(yy){
		if(yy&1){
			ret=(ret*xx)%n;
		}
		xx=(xx*xx)%n;
		yy>>=1;
	}
	return ret;
}

int main(void)
{
	scanf("%d",&T);
	while(T--){
		scanf("%lld%lld%lld%lld",&n,&d,&x,&y);
		if(x==y){
			printf("0\n");
			continue;
		}
		long long p,q;
		long long gd=exgcd(d,n,p,q);
		if((y-x)%gd!=0){
			printf("Impossible\n");
			continue;
		}else{
			long long ans=(y-x)/gd*p;
			long long m=n/gd;
			ans=(ans%m+m)%m;
			printf("%lld\n",ans);
			continue;
		}
	}
}

 Matrix Power Series

        题目描述

        给定n×n矩阵A和正整数k,求和S=A+A2+A3+…+Ak。

        输入

        输入只包含一个测试用例。
        第一行输入包含三个正整数n,k和m。
        接下来n行,每行包含n个非负整数(均不超过32,768),用以描绘矩阵A。

        输出

        按与描述矩阵A相同的方式,输出将S中所有元素对m取模后得到的矩阵。

        样例输入

        2 2 4
        0 1
        1 1

        样例输出

        1 2
        2 3

        提示

        【数据范围】

        1≤n≤30,

        1≤k≤10^9,

        1≤m<104

        题解: 

        这道题是真的很有价值,做了这道题之后对于矩阵的理解有所长进。这道题和一般的矩阵有点区别,一般题目矩阵里面的元素是一个数,而这道题的元素是一个矩阵!是的一拿到感到非常的头大,甚至有点不知所措(于是我考试就直接跳了这题)这道题的关键在于要有写矩形套矩形的勇气,主要的实践方法就是,写两个乘法,分别是单矩形的和矩形中的矩形的,需要注意的是大矩形中的1元素要转化为一个对角线都为1的小矩形,然后就是令人愉悦的快速幂部分。

        之前的题解提到,自己在写矩阵的时候大脑的运算速度十分低下,然而今天我找到了解决的方法,重载*符号

struct Matrix{
	long long m[120][120];
	Matrix operator *(Matrix b){
		Matrix c;
		memset(c.m,0,sizeof(c.m));
		for(int i=0;i<2*n;i++){
			for(int j=0;j<2*n;j++){
				for(int l=0;l<2*n;l++){
					c.m[i][j]=(c.m[i][j]+(m[i][l]*b.m[l][j])%mod)%mod;
				}
			}
		}
		return c;
	}
};

        这样写就极大的简化了思维复杂度,并且使代码不再那么冗长更方便debug(封装yyds)

那么完整代码如下:

#include<cstdio>
#include<cstring>
#include<string>
#include<cstring>
#include<string>

using namespace std;

long long n,k,mod;

struct Matrix{
	long long m[120][120];
	Matrix operator *(Matrix b){
		Matrix c;
		memset(c.m,0,sizeof(c.m));
		for(int i=0;i<2*n;i++){
			for(int j=0;j<2*n;j++){
				for(int l=0;l<2*n;l++){
					c.m[i][j]=(c.m[i][j]+(m[i][l]*b.m[l][j])%mod)%mod;
				}
			}
		}
		return c;
	}
};

inline Matrix solve(Matrix a,Matrix b)
{
	Matrix c;
		memset(c.m,0,sizeof(c.m));
		for(int i=0;i<n;i++){
			for(int j=0;j<2*n;j++){
				for(int l=0;l<2*n;l++){
					c.m[i][j]=(c.m[i][j]+(a.m[i][l]*b.m[l][j])%mod)%mod;
				}
			}
		}
		return c;
}

inline Matrix qpow(Matrix x, long long y)
{
	Matrix ret;
	memset(ret.m,0,sizeof(ret.m));
	for(int i=0;i<2*n;i++)ret.m[i][i]=1;
	while(y){
		if(y&1){
			ret=ret*x;
		}
		x=x*x;
		y>>=1;
	}
	return ret;
}

int main(void)
{
	Matrix a;
	memset(a.m,0,sizeof(a.m));
	scanf("%lld%lld%lld",&n,&k,&mod);
	for(int i=0;i<n;i++){
		for(int j=0;j<n;j++){
			scanf("%lld",&a.m[i][j]);
			a.m[i][j]%=mod;
		}
	}
	Matrix b;
	for(int i=0;i<n;i++){
		for(int j=0;j<n;j++)
			b.m[i][j]=a.m[i][j];
	}
	for(int i=n;i<2*n;i++){
		for(int j=0;j<n;j++){
			b.m[i][j]=a.m[i-n][j];
		}
	}
	for(int i=n;i<2*n;i++)b.m[i][i]=1;
	for(int i=0;i<n;i++)a.m[i][i+n]=1;
	
	b=qpow(b,k-1);
	Matrix ans;
	ans=a*b;
	for(int i=0;i<n;i++){
		for(int j=0;j<n;j++){
			printf("%lld ",ans.m[i][j]);
		}
		printf("\n");
	}
	
}

曹冲养猪

https://www.luogu.com.cn/problem/P1495icon-default.png?t=L892https://www.luogu.com.cn/problem/P1495

        题解:有三个方法

        法1:

        中国剩余定理:这里

#include<cstdio>
#define ll long long
ll n,a[16],m[16],Mi[16],mul=1,X;
inline int rd(){
    int io=0;char in=getchar();
    while(in<'0'||in>'9')in=getchar();
    while(in>='0'&&in<='9')io=(io<<3)+(io<<1)+(in^'0'),in=getchar();
    return io;
}
void exgcd(ll a,ll b,ll &x,ll &y){
    if(b==0){x=1;y=0;return ;}
    exgcd(b,a%b,x,y);
    int z=x;x=y,y=z-y*(a/b);
}
int main(){
    n=rd();
    for(int t=1;t<=n;++t){
        int M=rd();m[t]=M;
        mul*=M;
        a[t]=rd();
    }
    for(int t=1;t<=n;++t){
        Mi[t]=mul/m[t];
        ll x=0,y=0;
        exgcd(Mi[t],m[t],x,y);
        X+=a[t]*Mi[t]*(x<0?x+m[t]:x);
    }
    printf("%lld",X%mul);
    return 0;
}

        法2:

        一种非常巧妙的方法:

·        法3:

        这是一种比较一般的方法,当我们面对一个,单个同余方程时就比较好解决,上文有所提及那要是能将这几个同余方程合并成一个就好了

        那么拓展中国剩余定理就出来了,他比中国剩余定理强在,它可以处理除数不两两互质的情况。

        那么如何将两个同余方程合并呢?

        假设已经求出前k-1个方程组成的同余方程组的一个解为x且有


\large M=LCM^{k-1}_{i-1}m_i
 
则前k-1个方程的方程组通解为 \large x+i*M(i\in Z)

 

那么对于加入第k个方程后的方程组
我们就是要求一个正整数t,使得
\large x+t*M \equiv a_k(\mod m_k)

转化一下上述式子得
\large t*M \equiv a_k-x(\mod m_k)

对于这个式子我们已经可以通过扩展欧几里得求解t

\large t*M+m_k*y=a_k-x
若该同余式无解,则整个方程组无解
若有,则前k个同余式组成的方程组的一个解解为\large x_k=x+t*M

#include<cstdio>
#include<cmath>
#define ll long long
using namespace std;
int n;
long long a[10],m[10];

inline ll exgcd(ll c,ll b,ll &x,ll &y)
{
	if(b==0){x=1;y=0;return c;}
	ll d=exgcd(b,c%b,x,y);
	ll z=x;x=y;y=z-(c/b)*y;
	return d;
}

inline ll mul(ll x,ll y)
{
	ll ret=0;
	while(y){
		if(y&1)ret=(ret+x);
		x=(x+x);
		y>>=1;
	}
	return ret;
}

int main(void)
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%lld%lld",&m[i],&a[i]);
	bool flag=0;
	ll pr,p,q;
	for(int i=2;i<=n;i++)
	{
		ll d=exgcd(m[i-1],m[i],p,q);
		pr=mul(m[i-1],m[i])/d;
		ll k=((a[i]-a[i-1]))%pr;
		//if(k%d!=0){flag=1;break;}
		p=(p*k/d)%pr;
		a[i]=((m[i-1]*p+a[i-1]))%pr;
		m[i]=pr;
	}
	if(flag==1){printf("-1\n");return 0;}
	else printf("%lld\n",((a[n])%pr+pr)%pr);
	return 0;
}

Goldbach's Conjecture

        题目描述

        哥德巴赫猜想:任何大于 4 的偶数都可以拆成两个奇素数之和。 比如:     
        你的任务是:验证小于 106  的数满足哥德巴赫猜想。

        输入

        多组数据,每组数据一个 n。

        读入以 0 结束。

        输出

        对于每组数据,输出形如 n = a + b,其中 a,b 是奇素数。若有多组满足条件的 a,b,输出 b-a 最大的一组。
若无解,输出 Goldbach's conjecture is wrong.。

        样例输入

        8
        20
        42
        0

        样例输出

        8 = 3 + 5
        20 = 3 + 17
        42 = 5 + 37

        提示

        【数据范围与提示】

        对于全部数据,6≤n≤10^6 。

         题解:

        线性筛质数+映射,AC

#include<cstdio>
#include<cmath>

using namespace std;

int prime[300000];
int vis[1000020];
int cnt;
bool check[1000020];
int n;
bool flag=0;

int main(void)
{
	for(int i=2;i<=1000010;i++){
		if(vis[i]==0){
			cnt++;
			prime[cnt]=i;
			vis[i]=i;
		}
		for(int j=1;j<=cnt && prime[j]*i<=1000010;j++){
			vis[prime[j]*i]=prime[j];
			if(vis[i]<prime[j])break;
		}
	}
	for(int i=1;i<=cnt;i++)check[prime[i]]=1;
	//for(int i=1;i<=25;i++)printf("%d ",prime[i]);
	while(1){
		scanf("%d",&n);
		if(n==0)break;
		flag=0;
		for(int i=1;i<=cnt && prime[i]<=n/2;i++){
			if(check[n-prime[i]]==1){
				flag=1;
				printf("%d = %d + %d\n",n,prime[i],n-prime[i]);
				break;
			}
		}
		if(flag==0)printf("Goldbach's conjecture is wrong.\n");
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值