这周首先提到的一个点就是高精度计算,理解起来也很简单,用字符串模拟整数运算(浮点数不会,也还没了解,有过设想,分两个数组处理,但暂且不做尝试)。
(1)一般用数组下标0来记录位数,方便输出(乘法不能这么操作)
(2)先算完后进位或者边算边进位皆可,但一般边算边进位,效率较高
(3)善用加法
HDU - 1047 Integer Inquiry java代码
import java.util.*; ## 输入输出的库
import java.math.*; ## 数学库
public class Main{
public static void main(String[] args){
Scanner input = new Scanner(System.in); ## 定义输入
int n = input.nextInt(); ## 确认输入的n
while(n-- > 0){
BigInteger ans = new BigInteger("0");
while(input.hasNextBigInteger()){
BigInteger temp = new BigInteger("0");
temp = input.nextBigInteger();
if(!temp.equals(BigInteger.valueOf(0)) ){
ans = ans.add(temp);
}
else{
System.out.println(ans);
if(n != 0) System.out.println();
break;
}
}
}
input.close();
}
}
c++高精度加法模板 (摘自https://blog.csdn.net/kele52he/article/details/77484529)
#include
#include
#include
using namespace std;
const int L=110;
string add(string a,string b)//只限两个非负整数相加
{
string ans;
int na[L]={0},nb[L]={0};
int la=a.size(),lb=b.size();
for(int i=0;i<la;i++) na[la-1-i]=a[i]-'0';
for(int i=0;i<lb;i++) nb[lb-1-i]=b[i]-'0';
int lmax=la>lb?la:lb;
for(int i=0;i<lmax;i++) na[i]+=nb[i],na[i+1]+=na[i]/10,na[i]%=10;
if(na[lmax]) lmax++;
for(int i=lmax-1;i>=0;i--) ans+=na[i]+'0';
return ans;
}
int main()
{
string a,b;
while(cin>>a>>b) cout<<add(a,b)<<endl;
return 0;
}
摘的模板不够优化,其实可以自己按题目要求做一点简单的优化,下面是**wlacm 阶乘和(sum)**中对于加法的简单优化
#include
#include
using namespace std;
int a[1000] = {0},ans[1000] = {0};
void cal(){
int x = 0;
ans[0] = ans[0] > a[0] ?ans[0] :a[0]; ## 最高位二取一即可,对于两个大数,同理
for(int i = 1;i <= ans[0];i++){
ans[i] += a[i] + x;
x = ans[i] / 10;
ans[i] %= 10;
}
if(x) ans[++ans[0]] = x;
return;
}
int main()
{
int n;
cin >> n;
a[1] = 1;a[0] = 1;
ans[1] = 1;ans[0] = 1;
for(int i = 2;i <= n;i++){
int x = 0;
for(int j = 1;j <= a[0];j++){
a[j] = a[j] * i + x;
x = a[j] / 10;
a[j] %= 10;
}
while(x){
a[++a[0]] = x % 10;
x /= 10;
}
cal(); ## 自定义函数执行加法
}
for(int i = ans[0];i >= 1;i--) cout << ans[i];
cout << endl;
}
基本和上面加法同理,不同点是写个while循环进行减位操作和操作前比较两个数组大小,这里不再赘述,简单贴个模板,同样来自上面那个blog
#include
#include
#include
using namespace std;
const int L=110;
string sub(string a,string b)//只限大的非负整数减小的非负整数
{
string ans;
int na[L]={0},nb[L]={0};
int la=a.size(),lb=b.size();
for(int i=0;i<la;i++) na[la-1-i]=a[i]-'0';
for(int i=0;i<lb;i++) nb[lb-1-i]=b[i]-'0';
int lmax=la>lb?la:lb;
for(int i=0;i<lmax;i++)
{
na[i]-=nb[i];
if(na[i]<0) na[i]+=10,na[i+1]--;
}
while(!na[--lmax]&&lmax>0) ;lmax++;
for(int i=lmax-1;i>=0;i--) ans+=na[i]+'0';
return ans;
}
int main()
{
string a,b;
while(cin>>a>>b) cout<<sub(a,b)<<endl;
return 0;
}
和加减法同理,不同点两个高精度大数的乘法,必须要开两倍大的数组进行存储(这个自己想想就好,很简单),其次,数组的0下标位置不能再用来存位数,因为0的特性才使乘法得以成功
wlacm 高精度求积 代码
#include
#include
using namespace std;
int main()
{
int m[110] = {0},n[110] = {0},ans[220] = {0};
string a,b;
cin >> a >> b;
for(int i = a.size() - 1;i >= 0;i--) m[i] = a[a.size() - i - 1] - '0';
for(int i = b.size() - 1;i >= 0;i--) n[i] = b[b.size() - i - 1] - '0';
for(int i = 0;i < a.size();i++){
for(int j = 0;j < b.size();j++){
ans[i+j] = ans[i+j] + m[i] * n[j];
}
}
int ansl = a.size() + b.size();
for(int i = 0;i < ansl;i++){
ans[i + 1] = ans[i + 1] + ans[i] / 10;
ans[i] %= 10;
}
while(ansl > 1 && ans[ansl - 1] == 0) ansl--;
for(int i = ansl - 1;i >= 0;i--) cout << ans[i];
cout << endl;
}
做过,但没做过总结归纳,暂且放一放,不过也贴个代码
**hdu 1130 How Many Trees? && hdu 1023 Train Problem II **
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const ll k = 10000000000;
const int maxn = 105;
ll catalan[maxn][12];
void catalan_table(){
memset(catalan,0,sizeof(catalan));
catalan[0][0] = catalan[0][1] = 1;
int i,j;
for(i = 1;i <= 100;i++){
for(j = 1;j <= (int)catalan[i - 1][0];j++){
catalan[i][j] = (4*i-2) * catalan[i - 1][j];
}
ll r1 = 0,r2; ## 此处可以看做一个简单的除法模板,不考虑进位操作的裸模板
for(j = (int)catalan[i - 1][0];j >= 1;j--){
r2 = (r1 * k + catalan[i][j]) % (i + 1);
catalan[i][j] = (r1 * k + catalan[i][j]) / (i + 1);
r1 = r2;
}
for(j = 1;j <= (int)catalan[i - 1][0];j++){
catalan[i][j + 1] += catalan[i][j] / k;
catalan[i][j] %= k;
}
catalan[i][0] = catalan[i][j] > 0 ?catalan[i - 1][0] + 1 :catalan[i - 1][0];
}
}
int main()
{
catalan_table();
int n;
while(~scanf("%d",&n)){
printf("%lld",catalan[n][catalan[n][0]]);
for(int i = catalan[n][0] - 1;i >= 1;i--){
printf("%010lld",catalan[n][i]);
}
printf("\n");
}
return 0;
}
HDU - 1063 Exponentiation 浮点数高精计算
import java.util.*;
import java.math.*;
public class Main{
public static void main(String[] args){
Scanner input = new Scanner(System.in);
BigDecimal base;
while(input.hasNextBigDecimal()){
base = input.nextBigDecimal();
int n = input.nextInt();
String ans;
base = base.pow(n);
ans = base.stripTrailingZeros().toPlainString();
if(ans.startsWith("0.")) ans = ans.substring(1);
System.out.println(ans);
}
input.close();
}
}
从大一走过来,我们通常喜欢的幂的做法就是pow或者手写循环,但实际上对一些指数较大的幂函数来说,经常会力不从心,这里简单说一下快速幂的思想。
对于ab,我们实际上可以等价于(a2)b/2,这是显而易见的。
但我们不能简单地这么分,因为只有b为偶数时才能这么操作
所以我们把转化分为两种情况
当b & 1,ab == (a*a2)b/2
否则 ab == (a2)b/2
根据这个思想,我们可以把这个操作看做一层套一层的乘积,很显然能想到递归
int pow(int a,int b)
{
if(b==1)
return a;
if(b&1)
return a*pow(a*a,b/2);
return pow(a*a,b/2);
}
那在这个思想上再进一步深化,b实际上再整除的过程,形成了一串01串
那么这个式子,实际上,也就成了2k1+2k2+2k3+…+2kn = b
那么,我们不就能把递归转化成一个循环的形式进行优化了吗
int ans=1,base=a;
while(b)
{
if(b&1)
ans*=base;
base*=base;
b>>=1;
}
快速幂,实际上是一个二分思想上的优化,他不仅仅局限于幂,也同样适用于积
**HDU - 5666 Segment ** (快速积)
#include
using namespace std;
typedef long long ll;
int main()
{
ll t;
cin >> t;
while(t--){
ll q,P;
cin >> q >> P;
ll a = q - 2ll;
ll b = q - 1ll;
if(a & 1) b /= 2;
else a /= 2;
a %= P;
ll ans = 0;
while(b){
if(b & 1) ans = (ans + a) % P;
a = (a + a) % P;
b >>= 1;
}
cout << ans << endl;
}
return 0;
}
求矩阵An,说实话,第一次接触,也没写多少题,大概理解起来就是把简单的递推公式转换为矩阵,然后利用矩阵同样可以快速幂的特性,进行快速幂运算,以解决大数之下单纯递推超时的问题
难点:如何将递推关系转为矩阵
目前对这一块真的不了解,唯一做的题也是题目告诉你关系是怎么转换的,一个简单的finbo,目前就简单地贴一下代码吧
POJ - 3070 Fibonacci(写的很水,也很容易优化)
#include
#include
using namespace std;
typedef long long ll;
const ll mod = 10000;
ll maps[2][2],m[2][2];
void cal(){
ll ms[2][2];
memset(ms,0,sizeof(ms));
for(int i = 0;i < 2;i++){
for(int j = 0;j < 2;j++){
for(int k = 0;k < 2;k++){
ms[i][j] += maps[i][k] * m[k][j];
ms[i][j] %= mod;
}
}
}
memcpy(m,ms,sizeof(m));
return;
}
void cal2(){
ll ms[2][2];
memset(ms,0,sizeof(ms));
for(int i = 0;i < 2;i++){
for(int j = 0;j < 2;j++){
for(int k = 0;k < 2;k++){
ms[i][j] += maps[i][k] * maps[k][j];
ms[i][j] %= mod;
}
}
}
memcpy(maps,ms,sizeof(maps));
return;
}
int main()
{
ll n;
while(cin >> n && n != -1){
maps[0][0] = 1;
maps[0][1] = 1;
maps[1][0] = 1;
maps[1][1] = 0;
memset(m,0,sizeof(m));
m[0][0] = m[1][1] = 1;
ll k = n;
while(k){
if(k & 1) cal();
cal2();
k >>= 1;
}
cout << m[0][1] << endl;
}
return 0;
}
另外还有一道进阶题
HDU - 3117 Fibonacci Numbers
可以自行了解,这里特意提一下其中对于斐波那契数列中数取前四位的操作,其实可以成为一个不错的通用思路
斐波那契数列的通项公式为:f(n) = (1 / sqrt(5)) * (((1 + sqrt(5)) / 2) ^ n - ((1 + sqrt(5)) / 2) ^ n),当n >= 40时((1 + sqrt(5)) / 2) ^ n近似为0。
所以我们假设f(n) = t * 10 ^ k(t为小数),所以当两边同时取对数时,log10(t * 10 ^ k) = log10(t) + k = log10((1 / sqrt(5)) * (((1 + sqrt(5)) / 2))) = log10(1 / sqrt(5)) + n * log10(((1 + sqrt(5)) / 2))),
然后减掉整数k,就可以得到log10(t),进而得到t值。
最后附上代码
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const ll mod = 10000;
ll maps[2][2],m[2][2];
ll f[40];
void f_table(){
f[0] = 0;f[1] = 1;f[2] = 1;
for(int i = 3;i <= 39;i++) f[i] = f[i - 1] + f[i - 2];
return;
}
void cal(){
ll ms[2][2];
memset(ms,0,sizeof(ms));
for(int i = 0;i < 2;i++){
for(int j = 0;j < 2;j++){
for(int k = 0;k < 2;k++){
ms[i][j] += maps[i][k] * m[k][j];
ms[i][j] %= mod;
}
}
}
memcpy(m,ms,sizeof(m));
return;
}
void cal2(){
ll ms[2][2];
memset(ms,0,sizeof(ms));
for(int i = 0;i < 2;i++){
for(int j = 0;j < 2;j++){
for(int k = 0;k < 2;k++){
ms[i][j] += maps[i][k] * maps[k][j];
ms[i][j] %= mod;
}
}
}
memcpy(maps,ms,sizeof(maps));
return;
}
int main()
{
ll n;
f_table();
while(cin >> n){
if(n <= 39) cout << f[n] << endl;
else{
maps[0][0] = 1;
maps[0][1] = 1;
maps[1][0] = 1;
maps[1][1] = 0;
memset(m,0,sizeof(m));
m[0][0] = m[1][1] = 1;
ll k = n;
while(k){
if(k & 1) cal();
cal2();
k >>= 1;
}
double x = log10(1.0/sqrt(5.0)) + (double)n * log10((1.0+sqrt(5.0)) / 2.0);
double y = x - (int)(x);
int ans = (int)(1000.0 * pow(10.0,y));
printf("%d...%04lld\n",ans,m[0][1]);
}
}
return 0;
}
最后再接一句,关于快速幂还有很多地方能用,费马小定理求逆元,快速幂取模,它是实现一些算法最基础的部分,要深刻地理解
欧几里得算法,算是接触的最早的算法之一了,但现在才发明,我其实只懂一层皮毛
我们都知道,gcd能找出两个数a和b的最大公因数,但我们是否思考过,将a和b除以他们的最大公因数能得到什么?
答案很简单,设最大公因数为k:
gcd(a,b) / k = 1是必然的
而gcd(a,b) / k = 1实际上可以等价于gcd(a/k,b/k) = 1
那我们能够得到的,正好是a和b互质情况下最小的值
再反过来想一想,我们是否就能用这个想法得到一个结论
gcd(ak,bk) 只要a和b互质,那么gcd的值永远不会变化
本来对于这种东西是完全没有思考的,知道我做到了下面这个题目
HDU - 5584 LCM Walk(数论 + GCD + LCM)
题解,来自大佬
①我们可以发现当前位置是(x,y)时,如果x>y,那么当前位置一定是由(x1,y)走到的,如果xy,x=nk,y=mk,当前点就是由(x/(y/k+1),y)走到的,如果x不再是(y+k)的倍数(即:(y/k+1)*k的倍数),则表示不能再逆推
摘自blog:https://blog.csdn.net/qq_31759205/article/details/52628889
下面是代码
#include
using namespace std;
typedef long long ll;
ll gcd(ll a,ll b){
return !b ?a :gcd(b,a%b);
}
int main()
{
ll t,cnt = 0;
cin >> t;
while(t--){
ll x,y,ans = 1;
cin >> x >> y;
ll g = gcd(x,y);
if(x > y) swap(x,y);
while(!(y % (x + g))){
ans++;
x = x;
y = y / (g + x)*g;
if(x > y) swap(x,y);
}
printf("Case #%lld: %lld\n",++cnt,ans);
}
return 0;
}
这个性质还是比较好想的,上个学期也是做过多个数GCD的一个简单dp了,两个两个处理过去就好,新生的和老的继续GCD,LCM也是如此。
(这个就不列举题目了。。。)
gcd(x,n) = 1,1 <= x <= n;
本质上因为这个一块不应该放在gcd里面讲的,但因为自己第一次写是从gcd里得到的灵感,姑且写到gcd里面。
做一个简单的假设,假设x和n互质,那么没有问题,这就是我们要找的数
如果不是,那么就有意思了,x*k和n必然不可能互质,可以结合我们讲的一点来看
那么我们如果从2开始枚举,利用筛法的思路,把所有gcd(x,n) != 1的值为基数,做一次筛法,是不是我们就能
把所有和n不互质的数找出来了,剩下的不就都是互质的了吗
(当时不知道欧拉函数和容斥原理)
后面结合容斥原理和质因数分解定理,很容易就能写这题
当然直接写欧拉函数更好
简单来说,扩欧就是用来找二元一次方程的特解,然后根据题目来确认是否找通解之类的
设方程ax+by = n gcd(a,b) = k;
a = ka',n = kb' ax + by = k(a'x + b'y) = n
如果所有数都为整数,那么n必须为gcd的倍数才有解
很多时候,为了简化问题,判断完是否有解后,直接ax+by = gcd(a,b)
算出特解后整数倍扩大即可
实际上emm 扩欧学的还是很差,只能写写一些裸题,也只写了两道来着
POJ - 1061 青蛙的约会
自己写的时候非常的粗暴。。。套书上的意思,改一下方程的形式,写成类似不定方程的样子直接扩欧解的,写的时候还是很青涩,ac的也有一点莫名其妙
后面附上大佬的详细证明题解
来自洛谷 https://www.luogu.org/problemnew/solution/P1516
#include
#include
#include
#include
using namespace std;
typedef long long ll;
ll extend_gcd(ll a,ll b,ll &x1,ll &y1,ll &ans){
if(!b){
x1 = 1;y1 = 0;
return a;
}
ans = extend_gcd(b,a%b,x1,y1,ans);
ll t = x1;
x1 = y1;
y1 = t - a/b*y1;
return ans;
}
int main()
{
ll x,y,m,n,l,x1,y1,ans;
cin >> x >> y >> m >> n >> l;
ll a = x-y,b = n-m;
if(b < 0){
a = -a;
b = -b;
}
ans = extend_gcd(b,l,x1,y1,ans);
if(a % ans != 0) cout << "Impossible" << endl;
else cout << ((x1*(a/ans)) % (l/ans) + (l/ans))%(l/ans) << endl;
return 0;
}
HDU - 1576 A/B
这题emm 刚开始写一直卡自己的想的样例,看了题解才发现有可能有负数情况
直接附上大佬题解,自己只是硬套扩欧求不定方程的模板,实际上理解还是不行
https://blog.csdn.net/infinity_izayoi/article/details/51910343
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const ll mod = 9973;
void extend_gcd(ll a,ll b,ll &x,ll &y){
if(!b){
x = 1;y = 0;
return;
}
extend_gcd(b,a%b,x,y);
ll temp = x;
x = y;
y = temp - (a/b)*y;
return;
}
int main()
{
ll t;
cin >> t;
while(t--){
ll n,b,x,y;
cin >> n >> b;
extend_gcd(b,mod,x,y);
x = (x + mod) % mod;
x = (x*n) % mod;
cout << x << endl;
}
return 0;
}
简单表述同余定理,即a和b对c取余的余数相同,一般用于解决鸽笼问题,用等式方式处理整除关系,求逆元
pis:关于用用等式方式处理整除关系和逆元结合这一块,知之甚少,或者说,理解很不到尾,不敢随意做总结,估计放一放
1.鸽笼原理
何为鸽笼原理