P1890 gcd区间
题目传送门
前言:依旧是一道水题,看着那么多人再用线段树,我都懵了(因为我不会),本以为这道题只能用线段树,但是再仔细一看,好像可以用DP来做,所以就有了这篇题解(没错就是这么来的)。
题解:
DP的维度分析,以及DP意义:
首先这道题在询问的时候有两个参数,一个是l,另一个是r,询问得出的结果是区间 [l,r] ,所有数的最大公因数,所以我们可以这样,定义一个二维DP数组,横纵两个坐标表(i,j)表示从l到r这个区间内的最大公因数,这个做法有点类似于区间DP,但是要比区间简单
DP的状态转移方程:
我们先来看一个表格:
我们来看一下dp[i][j]是怎么得出来的,首先我们知道dp[i][j]是从第i个数到第j个数的最大公因数,所以我们应当找到dp[i][j]与前面dp数组的关系,我们知道有一个公式:
这个公式的意思就是三个数的最大公因数是其中两个数的最大公因数与第三个数的最大公因数,因为有了这个公式,我们就可以把gcd以及括号内的数替换成dp数组中的元素,以此来求出状态转移方程,我们可以假设gcd(a,b,c)为dp[i][j],那么gcd(a,b)就是dp[i[[j-1],则c就是a[j],把他带进去就可以得出状态转移方程(如下):
最后就知道了dp[i][j]=gcd(dp[i][j-1],a[j])就是我们最终的状态转移方程了,然后对着状态转移方程敲代码这道题就能轻松AC了。
gcd函数:
这个函数我们可以使用辗转相除法来求(也称欧几里得算法),利用被除数和除数的gcd为除数和余数的gcd是一样的结论,转而可以求解(这里有问题:NOIP是不允许用带下划线的gcd函数),所以咱们就乖乖手敲把(qwq):
int gcd(int a,int b){if(a%b==0){return b;}else{return gcd(b,a%b);}
}
AC代码:
#include<bits/stdc++.h>
using namespace std;
int gcd(int a,int b){if(a%b==0) return b;else return gcd(b,a%b);
}
int main(){int n,m;scanf("%d%d",&n,&m);int a[1005];int dp[1005][1005];for(int i=1;i<=n;i++){scanf("%d",&a[i]);}for(int i=1;i<=n;i++){for(int j=1;j<=n;j++){if(i==j){dp[i][j]=a[j];}else{dp[i][j]=gcd(dp[i][j-1],a[j]);}}}while(m--){int l,r;scanf("%d%d",&l,&r);printf("%d",dp[l][r]);printf("\n");}return 0;
}