Modulo Ruins the Legend题解&&同余

1.题目描述

本题来源:2022-ICPC-杭州-A

本人第一次发布题解博客,这道题在我自己学校oj上做到了,想以此记录这两天对这道题及背后算法原理的思考。由于新人刚入坑算竞,如有错误,敬请指正。

时间限制:C/C++ 1000MS,其他语言 2000MS
内存限制:C/C++ 1024MB,其他语言 2048MB

题目描述

Grammy has a sequence of integers a1​,a2​,…,an​. She thinks that the elements in the sequence are too large, so she decided to add an arithmetic progression to the sequence. Formally, she can choose two non-negative integers s,d, and add s+kd to ak​ for each k∈[1,n].

Since we want to ruin the legend, please tell her the minimum sum of elements modulo m after the operation. Note that you should minimize the sum after taking the modulo.

输入描述

The first line contains two integers n,m (1≤n≤105, 1≤m≤109).

The second line contains n integers a1​,a2​,…,an​ (0≤ai​<m), denoting the initial sequence.

输出描述

Output exactly two lines.

The first line contains one integer, denoting the minimum sum of elements modulo m.

The second line contains two integers s,d (0≤s,d<m), denoting the integers chosen by Grammy. If there are multiple solutions, output any.

样例输入 1 

6 24
1 1 4 5 1 4

样例输出 1 

1
0 5

样例输入 2 

7 29
1 9 1 9 8 1 0

样例输出 2 

0
0 0

题目大意:a1--an给每个数加上s+id(i>=1&&i<=n),求得总和再对m取模,使得结果(余数)最小,并输出一组使之满足的s和d

翻译成数学语言:

sum=\sum_{i=1}^{n}ai;ans为最小余数(答案);

则(sum+ns+d*(n+1)*n/2)%m=ans;

那么,如何找这个最小值呢,肯定不能爆搜吧(就算搜也得有迹可循吧),那么就不得不搬出数论中一个非常重要的知识了---同余(看了好久才能理解,又反复敲才能稍微会用)

2.相关芝士摘要

1.同余

概念:设 n 是给定的正整数,若整数 a、b 满足 m | (a − b),则称 a 和 b 模 n 同余, 记作 a ≡ b (mod m)。反之,则称 a 与 b 模 n 不同余, 记作 a ̸≡ b (mod m)。

性质:

• 反身性:a ≡ a (mod m))。

• 对称性:a ≡ b (mod m)) ⇐⇒ b ≡ a (mod m))。

• 传递性:a ≡ b (mod m)) ∧ b ≡ c (mod m)) ⇒ a ≡ c (mod m))。

• 可加性:a ≡ b (mod m)) ∧ c ≡ d (mod m)) ⇒ a ± c ≡ b ± d (mod m)。

• 可乘性: a ≡ b (mod m)) ∧ c ≡ d (mod m)) ⇒ ac ≡ bd (mod m))。

2.贝祖/裴蜀定理

贝祖 (裴蜀) 定理 (Bézout’s Lemma):存在整数 x, y, 使得 ax + by = gcd(a, b) 成立。因此当 gcd(a, b) | c 时方程有解。 其中 ax + by = gcd(a, b) (gcd为最大公因数)称为贝祖等式 (Bézout’s Identity)。 一个重要推论:gcd(a, b) = 1 ⇐⇒ ∃x, y ∈ Z, 使得 ax + by = 1。

举个栗子:

若 (bas1*x)%m=w( 记作a ≡ b (mod m)  )

=> (bas1*x)= m*(-y)+w

=>bas1*x+m*y=w  w=k*gcd(bas1,m)

这条定理很重要,下面代码用到的核心板子exgcd就用的这个定理。

重要的事情说三遍!!!exgcd函数解出的x,y对应的是 ax + by = gcd(a, b)这个方程的解(无论x,y怎么传参)x,y可能还是负解。

3.扩展欧几里得算法

下面介绍贝祖等式的一种解法,即扩展欧几里得算法 (Extended Euclid’s Algorithm), 该算法运用了从特殊到一般的递归思想。

当 a = gcd(a, b), b = 0 时,贝祖等式化为 gcd(a, b)*x = gcd(a, b), 一组解为 x=1,y=m,(m ∈ Z)。由于已经知道末状态的通解,我们只需找出相邻两个方程通解之间的 联系,即可递推求出贝祖等式的通解。

b*x1 + (a mod b)*y1 = b*x1 + (a − ⌊ \frac{a}{b} ⌋*b)*y1

                                = a*y1 + b*(x1 − ⌊ \frac{a}{b} ⌋*y1)

                                = a*x2 + b*y2

因此我们得到了一组递归式,即 x2 = y1, y2 = x1 − ⌊ \frac{a}{b} ⌋*y1

所以我们得到了exgcd板子:

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

3.数学推导

a = n; b = (n+1)*n/2; sum = \sum_{i=1}^{n}ai;

\because a*s+b*d+sum ≡ ans(mod m);

\therefore a*s+b*d ≡ (ans-sum)(mod m);

设a*s+b*d = A ;  ans-sum = B;

则A ≡ B(mod m);

设 bas1 = gcd(a,b);

\because a*s+b*d = bas1可以得出解  s1,d1;

\therefore  A = k*bas1;

\therefore s = k*s1 ; d = k*d1;

现在求k:

\because k*bas1 ≡ B(mod m);

\therefore k*bas1+m*y = B 解得 k1,y1;

设 bas2 = gcd(bas1,m);

\because  k*bas1+m*y = bas2

\therefore B = w*bas2

求ans:

\because ans - sum = B = w*bas2

\therefore sum = ans+w*bas2(变号)

\therefore sum % bas2 = ans

求s,d:

w=(ans - sum)/bas2

\because k1* bas1 + m*y1 = bas2

    k* bas1 + m*y = B

\because B = w*bas2

\therefore  k = k1*w(对m取模)

\therefore s=k*s1(对m取模操作保证为正且不爆int)

    d=k*d1(对m取模操作保证为正且不爆int)

4.ok,上代码

#include<bits/stdc++.h>
#define int long long
using namespace std;

//板子 这里求出的x,y是方程ax+by=__gcd(a,b)的解
int exgcd(int a, int b, int &x, int &y) {
    if(b == 0) { x=1; y=0; return a; }
    int gcd = exgcd(b, a%b, y, x);
    y -= (a/b) * x;
    return gcd;
}

signed main(){

    int n,m,x;
    cin>>n>>m;
    int sum=0;
    for(int i=0;i<n;i++){
		cin>>x;
        sum+=x;
    }
    int a=n;
    int b=(n+1)*n/2;//换个元好看一点
    int bas1=__gcd(a,b);
    int bas2=__gcd(bas1,m);
    int k,y1;
    int gcdd=exgcd(bas1,m,k,y1);
    int ans=sum%bas2;
   	k=(k*((ans-sum)/bas2%m))%m;//这里每一步要对m取模,防止爆int
    int s,d;
    int w=exgcd(a,b,s,d);
   	s=((s*k)%m+m)%m;//这里控制答案为正,同时防止爆
    d=((d*k)%m+m)%m;    
    cout<<ans<<endl;
    cout<<s<<" "<<d;
    
    return 0;
}

That is all,thank you!

  • 21
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值