一.题目求为:S(2004^X)mod 29
涉及的知识点:
1.一个数的因子和是积极函数(即F(ab)=F(a)F(b))
证明:
S(x)表示x的因子和。
如果x可以分成a,b(一定为素数),那么S(x)=S(a)S(b)。
为什么一定要分成素数呢,因为一个素数的因子只有1和它本身,对于a,b 来说,就是1,a,1,b,那么x=ab,x的因子只有1,a, b,x这四个数。
2.等比数列公式
比如题目跟29取模算法:S(2004^X) % 29 = (2^(2X+1)-1) % 29 * (3^(X+1)-1)/2 % 29 * (167^(X+1)-1)/166 % 29,
3.乘法中可以ab%c = (a%c * b%c)%c, 加法中也可以有(a+b+c)%c = (a+(b+c)%c)%c,减法也是有的,和加法一样,可是题目是除法所以需要知道逆元:比如a/b % c 可以改成 a * b^(c-2)%c,
其中b^(c-2)%c就是b的逆元
4.费马定理;
费马小定理(Fermat Theory)是数论中的一个重要定理,其内容为: 假如p是质数,且gcd(a,p)=1,那么 a^(p-1)%p=1。
由同余德定理得,a^(p - 1)%p = 1%p = 1。
题解(圈重点了,变换过程):
a/b % c = a * b^(-1) % c = a * b^(-1) * 1 % c
由上述第第四个知识点代换1得:a * b^(-1) * b^(c-1) % c = a * b^(c-2) % c。综上知识点就可以求解了。
#include< iostream>
#include< algorithm>
#include< cstdio>
#include< cstring>
#include< string>
#include< cmath>
#include< map>
using namespace std;
int Mod(int a, int b, int c) {//快速幂取模算出a的b次方模c的结果
int ans = 1;
while (b) {
if (b & 1) {
ans = ans * a % c;
}
b >>= 1;
a = a * a % c;
}
return ans;
}
int main()//如果看n有点不习惯,就按照上面的习惯看,把n换成x就行了
{
//freopen(“input.txt”, “r”, stdin);
int n;
while (scanf("%d", &n), n) {
int a = Mod(2, 2 * n + 1, 29) - 1;//求S(a)
int b = (Mod(3, n + 1, 29) - 1) * Mod(2, 27, 29);//求S(b)
int c = (Mod(22, n + 1, 29) - 1) * Mod(21, 27, 29);//这里把167换成了22,是因为22与167对29是同余的,所以简化一下,求S©
printf("%d\n", a * b * c % 29);//这里就是答案,S(答案) = S(a) * S(b) * S©,还不理解就上去看看奇性函数= =
}
return 0;
}
二.求Xa + Yb = 1时的最小X,Y
扩展欧几里得算法:是欧几里得算法(又叫辗转相除法)的扩展。已知整数a、b,扩展欧几里得算法可以在求得a、b的最大公约数的同时,能找到整数x、y(其中一个很可能是负数),使它们满足贝祖等式
如果a是负数,可以把问题转化成 ,然后令x’=(-x)。
通常谈到最大公约数时,我们都会提到一个非常基本的事实:给予二个整数a、b,必存在整数x、y使得ax + by = gcd(a,b)。
有两个数a,b,对它们进行辗转相除法,可得它们的最大公约数——这是众所周知的。然后,收集辗转相除法中产生的式子,倒回去,可以得到ax+by=gcd(a,b)的整数解。
扩展欧几里得算法可以用来计算模反元素(也叫模逆元),而模反元素在RSA加密算法中有举足轻重的地位。
#include< iostream>
#include< cstdio>
#define ll long long int
using namespace std;
ll solve(ll a,ll b,ll &x,ll &y)
{
if(b==0)
{
x=1; y=0;
return a;
}
ll d=solve(b,a%b,x,y);
ll xx=x;
x=y;
y=xx-a/b*y;
return d;
}
int main()
{
ll a,b;
while(cin>>a>>b)
{
ll x,y;
if(solve(a,b,x,y)==1)
{
while(x<0)
{
x+=b;
y-=a;
}
printf("%lld %lld\n",x,y);
}
else
printf(“sorry\n”);
}
return 0;
}
三.给你一个n和k,你要先求出2008的n次方的所有因子之和m,当然m是因子和对k取余的结果,最后求 2008的m次方%k
#include< iostream>
#include< cstdio>
#include< cmath>
#include< cstring>
#define ll long long int
using namespace std;
ll n,k;
int solve(ll a,ll b,ll c)
{
ll x=a;
ll sum=1;
while(b)
{
if(b%2==1)
{
sum=sumx%c;
}
x=xx%c;
b/=2;
}
return sum;
}
int main()
{
while(cin>>n>>k&&n&&k)
{
ll s1,s2,s;
s1=solve(2,3*n+1,k*250)-1;
s2=solve(251,n+1,k*250)-1;
s=s1*s2/250%k;
cout<<solve(2008,s,k)<<endl;
}
}
四.This time I need you to calculate the f(n) . (3<=n<=1000000)
f(n)= Gcd(3)+Gcd(4)+…+Gcd(i)+…+Gcd(n).
Gcd(n)=gcd(C[n][1],C[n][2],……,C[n][n-1])
C[n][k] means the number of way to choose k things from n some things.
gcd(a,b) means the greatest common divisor of a and b
题解:写出几项结合组合数的公式可以发现。当k的因子中不止一个素因子时gcd(k)=1,只有一个素因子时gcd(k)=该素因子。然后前几天学的素数筛法就很巧妙的筛出了这两种k。
#include< iostream>
#include< cstdio>
#include< cstring>
#include< algorithm>
#include< cmath>
using namespace std;
#define ll long long
const int N = 1000000 + 5;
const int mod = 1000000000 + 7;
int n,prime[N];
ll ans;
int main(){
memset(prime,0,sizeof(prime));//这个初始化很关键
for(int i=2;i<N;i++)
{
if(!prime[i])//自身是否是素数
{
prime[i]=i;
for(int j=prime[i]+prime[i];j<N;j+=prime[i])//素数的倍数筛
{
if(!prime[j])//只有一个素数
{
prime[j]=i;
}
else//大于等于两个素数
{
prime[j]=1;
}
}
}
}
while(cin>>n)
{
ans=0;
for(int i=3;i<=n;i++)//累计求和
{
ans+=prime[i];
}
printf("%lld\n",ans);
}
return 0;
}
五.
#include < iostream>
#include < cstdio>
#include < cstring>
#include < algorithm>
#include < cmath>
using namespace std;
int main()
{
int n,m;
while(~scanf("%d %d",&m,&n))
{
printf("%d %d\n",(m-1)n-m,(n-1)(m-1)/2);
}
return 0;
}
六。Codeforces Round #642 (Div. 3)D. Constructing the Array
题意:
给你一个长度为n的数组a初始元素全为0,每次把含0最多的子序列的中间元素设为当前的操作次数,输出最后不含0的a数组内所有元素。
刚开始没咋看懂题意后来才懂题意。这一题我自己做的时候用普通的数组去组合写,用循环判断写只对了一组数据。看题解运用了优先队列。遇到了这个个知识点虽然老师讲了,但是没用基本忘了。就又要学习补充一下知识点了。
思路:普通的队列是不行的,因为这里是先对含0最多并且靠左边的区间操作。那么就用优先队列定义以下优先级就可以了。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int N=2e5+10;
int step=0,n;
struct node
{
int l,r;
node(int left=0,int right=0):l(left),r(right){}
bool operator<(const node &a)const
{
int s1=r-l+1;
int s2=a.r-a.l+1;
if(s1== s2) return l>a.l;//含0一样多就优先靠左边
return (r-l+1)<(a.r-a.l+1);//含0多优先
}
};
int a[N];
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int t;
cin>>t;
while(t–)
{
step=0;
cin>>n;
priority_queueq;
q.push(node(1,n));
while(!q.empty())
{
node u=q.top();
q.pop();
int l=u.l,r=u.r,mid;
if((r-l+1)%2==0) mid= (l+r-1)/2;
else mid=(l+r)/2;
a[mid]=++step;
if(l<=mid-1) q.push(node(l,mid-1));//区间存在就入队
if(r>=mid+1) q.push(node(mid+1,r));
}
for(int i=1;i<=n;i++) cout<<a[i]<<" “;
cout<<endl;
}
return 0;
}
七.题目链接
A.刚开始费了挺大劲才把题目看懂,后面写出来一直超时看了大佬的代码才知道就差在一个条件,经过这次”教训“,以后遇见代码超时情况,应该再仔细看题目有没有特殊点,这个题目的特殊点就是等于0的时候就不用计算了大大缩短了时间,实在找不到就只能再看有没有办法改变算法。
#include<bits/stdc++.h>
using namespace std;
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T;
cin>>T;
while(T–)
{
long long n,k;
cin>>n>>k;k–;
while(k–)
{
long long temp=n;
long long ma=0,mi=9;
while(temp)
{
ma=max(ma,temp%10);
mi=min(mi,temp%10);
temp=temp/10;
}
if( mi == 0)//就是这个破东西,卡了我。
break;
n=n+ma*mi;
}
cout<<n<<endl;
}
return 0;
}
B.这个题目其实不难认真看看,就会找到特殊点,题目的举例只符合题意不会给你规律的,没太大用就是理解题意用的,只要把数组从小到大排序,每次递增到当前位置就可以分一组,循环就可以了。考察贪心思想。
#include<bits/stdc++.h>
using namespace std;
int a[200010];
int main()
{
int T;
cin>>T;
while(T–)
{
int n,temp=0,cnt=0;
cin>>n;
for(int i=1;i<=n;i++)
scanf(”%d",&a[i]);
sort(a+1,a+1+n);
for(int i=1;i<=n;i++)
{
if(temp+1>=a[i])
{
temp=0;
cnt++;
}
else
temp++;
}
cout<<cnt<<endl;
}
return 0;
}
C.出题人把1800的题放这里,1400分的题放在D,我开始暴力枚举又超时。动态规划算法+数学转化;
#include <bits/stdc++.h>
using namespace std;
int a, b, c, d;
int B, C;
long long Calc(int x)
{
return min(B, x - a) - max(0, x - a - C) + 1;
}
int main()
{
scanf("%d%d%d%d", &a, &b, &c, &d);
B = b - a, C = c - b;
long long Ans = 0;
for (int i = a ; i <= c ; ++ i)
Ans += 1ll * max(0, min(d, i + b - 1) + 1 - c) * Calc(i);
printf("%lld\n", Ans);
return 0;
}
D.
先贪心,直接把数组的前n-1个数,赋值成1,然后最后一个数赋值成s-(n-1)。
1.数组的前n-1个数的子数组的和的范围为:1到n-1。
2.包含最后一个数的子数组的范围为s-(n-1)到s。
3.我们再判断第一步的最大值(也就是n-1)是否大于或等于第二步的最小值-1。
#include <bits/stdc++.h>
using namespace std;
int main()
{
std::ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int n,s;
cin >> n >> s;
int Min = s-n+1,Max = n-1;
if(Max >= Min-1) cout << “NO” << endl;
else
{
cout << “YES” << endl;
for(int i = 1;i <= n-1;i++) cout << 1 << " ";
cout << Min << endl;
cout << Max+1 << endl;
}
}
总结:
练习,总的来说就是数学的的东西和思想怎么用代码的形式体现出来。学的那些费马小定理,欧基米德,扩展欧基米德,逆元等等。学的过程都是公式的形式体现出来,说实话有些根本没听懂,课后自己模拟几组数据才想的明白。自己用数据计算一遍才能发现其中的规律。课后自己推导理解才能更快的掌握。
比赛,总的来说这周打了好几场对于Div4,Div3的题目比较简单。更好出题。以前的Div2第一的思维量不是很高,都会出题。昨天一场的比赛,思维量比以前的做的都高。因为输入的次数很大所以以为输入了可以缩短cin和out的时间可以过都是超时就很打击心态。后来回想起来,如果当时放弃那一题,认真做后面题,出一题应该是没有问题的。总体感觉a.b的题目对代码基本没高要求,感觉只要读懂题意,找出特殊点,就能出题。