小总结:
今天我们把大本营从日新楼的机房搬到了国际部的四楼,在之后的一段时间之内我们都要在这里进行集训了,其实高二的学长们已经在这里上了一段时间的课了,还有一段时间就要进行高考了,不知道我们那几天该怎么安排,现在疫情这么严重,还不知道十一号那天是我们回家还是奥赛生回校呢。
今天其实我主要复习了一些数论的东西,通过这一天我也意识到了我的数学是有多么的渣......一道网上的'板子题'成功的磨掉了我一个小时的时间.
T1:仪仗队
这道题我们考虑一个斜率上的点我们只能看到一个,于是只有当gcd(x,y)==1的时候这个点才能看到,因为会有一行一列的点在坐标轴上,所以我们要先把第一行和第一列去除,之后对于一列,和他互质的数字都可以看到,这不就是欧拉函数吗?于是我们统计每一列和其欧拉函数的乘积,由于对称乘以二,最后加上第一行第一列的那三个点就可以了.
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define int long long 4 const int N=3e6+10; 5 int phi[N],vis[N],prime[N],tot,n; 6 void getphi(int n){ 7 phi[1]=1;vis[1]=1; 8 for(int i=1;i<=n;++i){ 9 if(!vis[i]){ 10 prime[++tot]=i; 11 phi[i]=i-1; 12 } 13 for(int j=1;j<=tot&&prime[j]*i<=n;++j){ 14 vis[prime[j]*i]=1; 15 if(i%prime[j]==0){ 16 phi[i*prime[j]]=phi[i]*prime[j]; 17 break; 18 } 19 else phi[i*prime[j]]=phi[i]*(prime[j]-1); 20 } 21 } 22 } 23 int ans=0; 24 signed main(){ 25 scanf("%lld",&n); 26 if(n==1){ 27 printf("0 "); 28 return 0; 29 } 30 n--; 31 getphi(n); 32 for(int i=2;i<=n;++i) 33 ans+=2*phi[i]; 34 ans+=3; 35 printf("%lld ",ans); 36 return 0; 37 }
T2:Longge的问题
考虑如果我们暴力进行计算,那么是一定会超时的,于是我们考虑计算每个数字对于答案的贡献,这个数字一定是n的一个因子,如果要使gcd(n,a)=i,必定有a=k×i(1≤k≤n/i)并且gcd(k,n/i)=1也就是求出区间[1,n/i]中与n/i互质的数的个数,这正是欧拉函数的定义同时也要把另一个因数搞一下,这样就可以求出这道题了.
1 #include<bits/stdc++.h> 2 #define int long long 3 using namespace std; 4 const int N=5e6+10; 5 int phi(int x){ 6 int ans=x; 7 int m=sqrt(x+0.5); 8 for(int i=2;i<=m;++i){ 9 if(x%i==0){ 10 ans=ans*(i-1)/i; 11 while(x%i==0) x/=i; 12 } 13 } 14 if(x>1) ans=ans*(x-1)/x; 15 return ans; 16 } 17 int ans=0; 18 signed main(){ 19 int n; 20 scanf("%lld",&n); 21 int m=sqrt(n+0.5); 22 for(int i=1;i<=m;++i){ 23 if(n%i==0){ 24 ans+=i*phi(n/i); 25 if(i*i!=n) ans+=n/i*phi(i); 26 } 27 } 28 printf("%lld ",ans); 29 return 0; 30 }
T3:青蛙的约会
其实这就是一道很板子的扩展欧几里德呀.....但是我还是太菜了,这都没看出来.....
考虑如果两只青蛙相遇,那么一定有(n−m)t≡(x−y)(mod l),我们对这个式子进行变形,就可以得到(n−m)t−kl=x−y,而我们标准的扩展欧几里德形式为a*x+b*y=gcd(a,b),于是我们考虑把它变形为(n−m)t−kl=gcd((n−m),l),这样求出答案后把修改后的结果再乘上(x−y)/gcd((n−m),l)变为原来结果就行了.
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define int long long 4 int exgcd(int a,int b,int &x,int &y){ 5 if(b==0){ 6 x=1;y=0; 7 return a; 8 } 9 int d=exgcd(b,a%b,x,y); 10 int temp=x; 11 x=y; 12 y=temp-(a/b)*y; 13 return d; 14 } 15 signed main(){ 16 int x,y,m,n,l; 17 scanf("%lld%lld%lld%lld%lld",&x,&y,&m,&n,&l); 18 int ans1,ans2; 19 int a=n-m,b=l,c=x-y; 20 if(a<0) a=-a,c=-c; 21 int Gcd=exgcd(a,b,ans1,ans2); 22 if(c%Gcd){ 23 printf("Impossible "); 24 return 0; 25 } 26 int ad=b/Gcd; 27 ans1=((ans1*(c/Gcd)%ad)+ad)%ad; 28 printf("%lld ",ans1); 29 return 0; 30 }
T4:倒酒
这道题中我们可以知道最后的剩下的值就是x=Pb*b-Pa*a,因为a和b要互质,所以x一定是gcd(a,b)的整数倍,不然让两边同时除以gcd(a,b),左边就变成了分数,这是不符合实际的,那x最小就是gcd(a,b)。于是我们的问题就转化为了用欧拉函数求一组Pb,Pa的最小解(题目要求最小),由于我们上面的式子之中的a是负的,于是我们要对x和a取反,之后不断加上b/gcd(a,b),当然b同时也要加上a/gcd,这样就解决了.
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=1e6+10; 4 int gcd(int x,int y){ 5 return y==0 ? x : gcd(y,x%y); 6 } 7 int exgcd(int a,int b,int &x,int &y){ 8 if(b==0){ 9 x=1;y=0; 10 return a; 11 } 12 int d=exgcd(b,a%b,x,y); 13 int temp=x; 14 x=y; 15 y=temp-(a/b)*y; 16 return d; 17 } 18 signed main(){ 19 int a,b,x,y; 20 scanf("%d%d",&a,&b); 21 int Gcd=exgcd(a,b,x,y); 22 x=-x;a=-a; 23 while(x<0||y<0){ 24 if(x<0) x+=b/Gcd; 25 if(x>=0) y-=a/Gcd; 26 } 27 printf("%d %d %d ",Gcd,x,y); 28 return 0; 29 }
一些差分约束的板子题目
其实这一块的内容在假期就已经表现出问题了,只是那时我只会划水,没有去及时弥补,今天下午用大约两个小时的时间对该内容进行了及时的修补,应该已经没有大问题了.
所谓差分约束就是把几个不等式的大小关系转化为图论之中的边权,从LC大佬那里我们可以知道当我们是把大于号的值转化为边权的时候,我们通过跑最长路可以求出最小花费,把小于号的值转化为边权的时候,我们通过跑最短路可以求出最大花费,如果两个点相等,建一条边权为0的双向边就可以了.
推荐板子练习:【模板】差分约束算法,P1993 小K的农场,P3275 [SCOI2011]糖果
这里放一个板子吧qwq
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=1e6+10; 4 struct Node{ 5 int next,to,dis; 6 }edge[N]; 7 int Head[N],tot; 8 void Add(int x,int y,int z){ 9 edge[++tot].to=y; 10 edge[tot].dis=z; 11 edge[tot].next=Head[x]; 12 Head[x]=tot; 13 } 14 int n,m; 15 queue<int>q; 16 int dis[N],vis[N],in[N]; 17 void Spfa(int x){ 18 memset(dis,128,sizeof(dis)); 19 memset(vis,0,sizeof(vis)); 20 memset(in,0,sizeof(in)); 21 q.push(x);vis[x]=1;dis[x]=0;in[x]=1; 22 while(!q.empty()){ 23 int u=q.front();q.pop();vis[u]=0; 24 for(int i=Head[u];~i;i=edge[i].next){ 25 int v=edge[i].to; 26 if(dis[v]<dis[u]+edge[i].dis){ 27 dis[v]=dis[u]+edge[i].dis; 28 if(!vis[v]){ 29 vis[v]=1; 30 q.push(v); 31 ++in[v]; 32 if(in[v]>n+1){ 33 printf("NO "); 34 exit(0); 35 } 36 } 37 } 38 } 39 } 40 } 41 int main(){ 42 scanf("%d%d",&n,&m); 43 memset(Head,-1,sizeof(Head)); 44 for(int i=1;i<=m;++i){ 45 int x,y,z; 46 scanf("%d%d%d",&x,&y,&z); 47 Add(x,y,-z); 48 } 49 for(int i=1;i<=n;++i) Add(0,i,0); 50 Spfa(0); 51 for(int i=1;i<=n;++i) printf("%d ",dis[i]); 52 puts(""); 53 return 0; 54 }
一些拓扑排序的板子题目
拓扑排序可以用于求DAG中的一条最长路径,当然只是适用于没有环的图,这样可以比对每个图中的点都跑一边最长路要方便快捷的多,这里一样还是放一个最长路板子吧qwq
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=1e6+10; 4 int dis[N]; 5 struct Node{ 6 int next,to,dis; 7 }edge[N]; 8 int Head[N],tot; 9 void Add(int x,int y,int z){ 10 edge[++tot].to=y; 11 edge[tot].next=Head[x]; 12 edge[tot].dis=z; 13 Head[x]=tot; 14 } 15 int n,m,ru[N]; 16 stack<int>sta; 17 void Tuopu(){ 18 for(int i=1;i<=n;++i) 19 if(!ru[i]) sta.push(i); 20 while(!sta.empty()){ 21 int u=sta.top();sta.pop(); 22 for(int i=Head[u];i;i=edge[i].next){ 23 int v=edge[i].to; 24 dis[v]=max(dis[v],dis[u]+edge[i].dis); 25 ru[v]--; 26 if(!ru[v]) sta.push(v); 27 } 28 } 29 } 30 int main(){ 31 scanf("%d%d",&n,&m); 32 for(int i=1;i<=m;++i){ 33 int x,y,z; 34 scanf("%d%d%d",&x,&y,&z); 35 Add(x,y,z);ru[y]++; 36 } 37 memset(dis,128,sizeof(dis)); 38 //printf("%d ",dis[0]); 39 dis[1]=0; 40 Tuopu(); 41 if(dis[n]==-2139062144) printf("-1 "); 42 else printf("%d ",dis[n]); 43 return 0; 44 }
一道A*的板子题目
我在写之前还在好奇为什么板子题是黑题的时候,写完才知道A*被卡了,只能用可持久性可并堆来做,难怪......但是通过特判还是可以解决的,这里还是只放一个板子吧.ZA的代码真难调
1 #include<bits/stdc++.h> 2 using namespace std; 3 struct Node{ 4 int next,to; 5 double dis; 6 }edge[200010],_edge[200010]; 7 int n,m; 8 double E; 9 int _Head[200010],_tot,Head[200010],tot; 10 void _Add(int x,int y,double z){ 11 _edge[++_tot].to=y; 12 _edge[_tot].dis=z; 13 _edge[_tot].next=_Head[x]; 14 _Head[x]=_tot; 15 } 16 void Add(int x,int y,double z){ 17 edge[++tot].to=y; 18 edge[tot].dis=z; 19 edge[tot].next=Head[x]; 20 Head[x]=tot; 21 } 22 struct Edge{ 23 int num; 24 double dis; 25 Edge(int x,double y){ 26 num=x;dis=y; 27 } 28 bool operator < (const Edge &a)const{ 29 return a.dis<dis; 30 } 31 }; 32 double _dis[5050]; 33 int vis[5050]; 34 priority_queue<Edge>_q; 35 void dijkstra(int x,int n){ 36 for(int i=0;i<=n;++i) _dis[i]=100000000; 37 memset(vis,0,sizeof(vis)); 38 _dis[x]=0;_q.push(Edge(x,0)); 39 while(!_q.empty()){ 40 int u=_q.top().num;_q.pop(); 41 if(vis[u]) continue; 42 vis[u]=1; 43 for(int i=_Head[u];i;i=_edge[i].next){ 44 int v=_edge[i].to; 45 if(_dis[v]>_dis[u]+_edge[i].dis){ 46 _dis[v]=_dis[u]+_edge[i].dis; 47 _q.push(Edge(v,_dis[v])); 48 } 49 } 50 } 51 } 52 struct astar{ 53 int num; 54 double hx,gx; 55 astar(int x,double y,double z){ 56 num=x;hx=y;gx=z; 57 } 58 bool operator < (const astar &a)const{ 59 return a.hx+a.gx<hx+gx; 60 } 61 }; 62 int ans1,k,cnt[5050]; 63 priority_queue<astar>q; 64 void Astra(int x){ 65 q.push(astar(x,_dis[x],0)); 66 while(!q.empty()){ 67 int u=q.top().num; 68 double dist=q.top().gx;q.pop(); 69 cnt[u]++; 70 if(cnt[u]>k) continue; 71 if(u==n){ 72 if((double)E<(double)dist) return; 73 E-=(double)dist; 74 ans1++; 75 } 76 for(int i=Head[u];i;i=edge[i].next){ 77 int v=edge[i].to; 78 q.push(astar(v,_dis[v],dist+edge[i].dis)); 79 } 80 } 81 } 82 int main(){ 83 scanf("%d%d%lf",&n,&m,&E); 84 for(int i=1;i<=m;++i){ 85 int x,y;double z; 86 scanf("%d%d%lf",&x,&y,&z); 87 Add(x,y,z);_Add(y,x,z); 88 } 89 dijkstra(n,n); 90 k=E/_dis[1]; 91 if(k>1000000){ 92 printf("2002000 "); 93 return 0; 94 } 95 Astra(1); 96 printf("%d ",ans1); 97 return 0; 98 }
总结:
感觉今天我还是复习了不少东西的,从拓扑排序到差分约束,基本都唤起了我那远古的回忆,从今天一天的数论复习之中我意识到我在数学方面还存在超级超级大的不足,连板子题都看不出要看题解的那种,但是在打A*没怎么调就过的感觉还是很棒的,代码能一次写对还是能带来很多的自信的,希望明天自己能够取得一个好成绩吧.