“科大国创杯”2023 年安徽省青少年信息学科普日活动 简要题解
老年退役选手感受单调队列力量。
初中组没有实现,如果有问题欢迎爆 d 我。已经补上代码了
小学组
T1 grade
直接累加即可。不需要按百分比算(也就是别 /100),那样可能会出现一些浮点数误差。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> typedef long long ll; using namespace std; inline int read(){ int x=0,f=1; char c=getchar(); while(c<'0'||c>'9'){ if(c=='-') f=-1; c=getchar(); } while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar(); return x*f; } int n,a[10005]; ll X; int main(){ freopen("grade.in","r",stdin); freopen("grade.out","w",stdout); n=read(); for(int i=1;i<=n;i++) a[i]=read(); for(int i=1;i<=n;i++) X+=read()*a[i]; for(int i=1;i<=n;i++) X-=read()*a[i]; if(X>0) puts("ke"); if(!X) puts("same"); if(X<0) puts("do"); return 0; }
T2 order
暴力枚举 $t$ 就可以了
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> using namespace std; inline int read(){ int x=0,f=1; char c=getchar(); while(c<'0'||c>'9'){ if(c=='-') f=-1; c=getchar(); } while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar(); return x*f; } int main(){ freopen("order.in","r",stdin); freopen("order.out","w",stdout); int tc=read(); while(tc--){ int p=read(),n=read(),q=n,t=1; while(q!=1) q=1ll*q*n%p,t++; printf("%d\n",t); } return 0; }
T3 string
答案即为 $cnt4+cnt5-cnt20$。注意到对于一个数,我们只关心其个位和十位就可以判定了,然后就是 $O(n)$。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> typedef long long ll; using namespace std; const int N=1000005; inline int read(){ int x=0,f=1; char c=getchar(); while(c<'0'||c>'9'){ if(c=='-') f=-1; c=getchar(); } while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar(); return x*f; } int n; char s[N]; ll X,Y,Z; int main(){ freopen("string.in","r",stdin); freopen("string.out","w",stdout); n=read(); scanf("%s",s+1); for(int i=1;i<=n;i++){ if(s[i]=='0'||s[i]=='5') X+=i; if(s[i]=='0'||s[i]=='4'||s[i]=='8') Y++; if(i>1 && ((s[i-1]-'0')*10+(s[i]-'0'))%4==0 ) Y+=i-1; if(s[i]=='0') Z++; if(i>1 && ((s[i-1]-'0')*10+(s[i]-'0'))%20==0 ) Z+=i-1; } printf("%lld\n",X+Y-Z); return 0; }
T4 hex
并查集维护连通性即可。注意如果不建虚点的话需要特判 $n=1$。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> using namespace std; inline int read(){ int x=0,f=1; char c=getchar(); while(c<'0'||c>'9'){ if(c=='-') f=-1; c=getchar(); } while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar(); return x*f; } int n,fa[10005],a[105][105]; inline int id(int i,int j){ return (i-1)*n+j; } inline int find(int x){ return fa[x]==x?x:fa[x]=find(fa[x]); } inline void merge(int x,int y){ int fx=find(x),fy=find(y); fa[fy]=fx; } inline void solve(){ n=read(); if(n==1){ int x=read(); if(x==1) puts("ke"); else puts("yet"); return; } for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) a[i][j]=read(); for(int i=1;i<=n*n;i++) fa[i]=i; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++){ if(j<n && a[i][j]==1 && a[i][j+1]==1) merge(id(i,j),id(i,j+1)); if(i<n && a[i][j]==1 && a[i+1][j]==1) merge(id(i,j),id(i+1,j)); if(i<n && j>1 && a[i][j]==1 && a[i+1][j-1]==1) merge(id(i,j),id(i+1,j-1)); } for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(find(id(1,i))==find(id(n,j))){ puts("ke"); return; } for(int i=1;i<=n*n;i++) fa[i]=i; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++){ if(j<n && a[i][j]==-1 && a[i][j+1]==-1) merge(id(i,j),id(i,j+1)); if(i<n && a[i][j]==-1 && a[i+1][j]==-1) merge(id(i,j),id(i+1,j)); if(i<n && j>1 && a[i][j]==-1 && a[i+1][j-1]==-1) merge(id(i,j),id(i+1,j-1)); } for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(find(id(i,1))==find(id(j,n))){ puts("do"); return; } puts("yet"); } int main(){ freopen("hex.in","r",stdin); freopen("hex.out","w",stdout); int tc=read(); while(tc--) solve(); return 0; }
初中组
T1 score
和小学组一样,不做除法就可以了。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> using namespace std; inline int read(){ int x=0,f=1; char c=getchar(); while(c<'0'||c>'9'){ if(c=='-') f=-1; c=getchar(); } while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar(); return x*f; } int n,m,a[105]; struct A{ int x,y; bool operator < (const A &a) const { return x==a.x?y<a.y:x>a.x; } }b[105]; int main(){ freopen("score.in","r",stdin); freopen("score.out","w",stdout); n=read(); m=read(); for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++) a[j]=read(); sort(a+1,a+m+1); b[i].y=i; for(int j=2;j<m;j++) b[i].x+=a[j]; } sort(b+1,b+n+1); for(int i=1;i<=n;i++) printf("%d%c",b[i].y,i==n?'\n':' '); return 0; }
T2 count
排好序,枚举一个 $i$,然后枚举 $j$ 的时候 $k$ 尺取一下,时间复杂度 $O(n^2)$。
记得开 long long。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> typedef long long ll; using namespace std; inline int read(){ int x=0,f=1; char c=getchar(); while(c<'0'||c>'9'){ if(c=='-') f=-1; c=getchar(); } while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar(); return x*f; } int n,a[8005]; ll ans; int main(){ freopen("count.in","r",stdin); freopen("count.out","w",stdout); n=read(); for(int i=1;i<=n;i++) a[i]=read(); sort(a+1,a+n+1); for(int i=1;i<=n;i++) for(int j=i+1,k=i+1;j<=n;j++){ while(k<n && a[i]+a[j]>a[k+1]) k++; ans+=k-j; } printf("%lld\n",ans); return 0; }
T3 walk
先找全局最短路。
如果询问边不在最短路上,直接输出全局最短路。
这样本质不同的询问个数就是 $O(n)$ 了。
然后可以预处理 $(1,1) \to (x,y)$ 和 $(x,y) \to (n,n)$ 的最短路 单次询问简单讨论一下 $O(n)$ 是容易的。
总时间复杂度 $O(n^2+\min(n,q)n)$,即 $O(n^2)$。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> #define y1 wtakioi using namespace std; typedef long long ll; const int N=5005; const ll inff=1ll<<60; int n,q,B,d[N<<1],a[N][N],b[N][N],x1,x2,y1,y2; ll f[N][N],g[N][N],ask[N<<1]; bool path[N][N]; unsigned long long seed; inline void xorshift64(){ seed^=seed<<13; seed^=seed>>7; seed^=seed<<17; } inline ll solve(int x,int y,int o){ // cerr<<"Ask "<<x<<' '<<y<<' '<<o<<'\n'; ll ans=inff; if(o){ for(int i=1;i<=x;i++) ans=min(ans,f[i][y+1]+g[i][y+1]); for(int i=1;i<y;i++) ans=min(ans,f[x+1][i]+g[x+1][i]); } else{ for(int i=1;i<x;i++) ans=min(ans,f[i][y+1]+g[i][y+1]); for(int i=1;i<=y;i++) ans=min(ans,f[x+1][i]+g[x+1][i]); } return ans; } int main(){ freopen("stone.in","r",stdin); freopen("stone.out","w",stdout); ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); cin>>n>>q>>seed>>B; for(int i=1;i<=n;i++){ for(int j=1;j<n;j++){ xorshift64(); a[i][j]=seed&((1<<B)-1); } } for(int i=1;i<n;i++){ for(int j=1;j<=n;j++){ xorshift64(); b[i][j]=seed&((1<<B)-1); } } for(int i=1;i<=n;i++) f[i][0]=f[0][i]=inff; f[0][1]=0; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++){ ll f1=f[i][j-1]+a[i][j-1],f2=f[i-1][j]+b[i-1][j]; if(f1<=f2) f[i][j]=f1,path[i][j]=0; else f[i][j]=f2,path[i][j]=1; } for(int x=n,y=n;x>1||y>1;){ if(path[x][y]==0) y--,d[x+y]=y; else x--,d[x+y]=x+n; } for(int i=1;i<=n;i++) g[n+1][i]=g[i][n+1]=inff; g[n+1][n]=0; for(int i=n;i;i--) for(int j=n;j;j--) g[i][j]=min(g[i][j+1]+a[i][j],g[i+1][j]+b[i][j]); while(q--){ cin>>x1>>y1>>x2>>y2; int op=x2-x1,z=op?x1+n:y1; if(d[x1+y1]!=z){ cout<<f[n][n]<<'\n'; continue; } if(!ask[x1+y1]) ask[x1+y1]=solve(x1,y1,op); cout<<ask[x1+y1]<<'\n'; } return 0; }
T4 stone
考虑贪心。如果在还没有合并的点中,最小值就在当前可以合并到的位置,那么我们一定会去合并它。
进一步的,以最小值在左边为例:设最小值在位置 $x$,我们可以直接将 $x$ 和 $x+1$ 看作一堆,因为合并到 $x+1$ 时,一定会立刻合并 $x$。
更进一步的,合并完之后的这若干堆石子,其贡献可以类似地看作它们的平均值。
于是考虑贪心在两边维护好这样的栈,以左边为例,从左向右扫,如果当前栈顶的平均值大于其下方的元素,则将它们合并。
数据随机下,栈内元素是 $O(\log n)$ 的。然后就预处理出每个栈的形态,询问时暴力地不停合并左右两个栈中较小的栈顶即可。
时间复杂度 $O(n \log n)$。
存在不依赖随机性质的做法。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> #define mp make_pair #define pii pair<ll,int> using namespace std; typedef long long ll; const int N=100005; const int M=31; inline int read(){ int x=0,f=1; char c=getchar(); while(c<'0'||c>'9'){ if(c=='-') f=-1; c=getchar(); } while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar(); return x*f; } int n,L,R,a[N],ltop[N],rtop[N]; pii lstc[N][M],rstc[N][M]; ll c1[N],c2[N],sum[N],ans; inline bool check(pii x,pii y){ return x.first*y.second>y.first*x.second; } inline pii add(pii x,pii y){ x.first+=y.first; x.second+=y.second; return x; } int main(){ freopen("stone.in","r",stdin); freopen("stone.out","w",stdout); n=read(),L=read(),R=read(); for(int i=1;i<=n;i++) a[i]=read(); for(int i=1;i<=n;i++){ ltop[i]=ltop[i-1]; for(int j=1;j<=ltop[i];j++) lstc[i][j]=lstc[i-1][j]; lstc[i][++ltop[i]]=mp(a[i],1); while(ltop[i]>1 && check(lstc[i][ltop[i]],lstc[i][ltop[i]-1])) lstc[i][ltop[i]-1]=add(lstc[i][ltop[i]-1],lstc[i][ltop[i]]),ltop[i]--; } for(int i=n;i;i--){ rtop[i]=rtop[i+1]; for(int j=1;j<=rtop[i];j++) rstc[i][j]=rstc[i+1][j]; rstc[i][++rtop[i]]=mp(a[i],1); while(rtop[i]>1 && check(rstc[i][rtop[i]],rstc[i][rtop[i]-1])) rstc[i][rtop[i]-1]=add(rstc[i][rtop[i]-1],rstc[i][rtop[i]]),rtop[i]--; } for(int i=n;i;i--) c1[i]=c1[i+1]+1ll*a[i]*i; for(int i=1;i<=n;i++) c2[i]=c2[i-1]+1ll*a[i]*(n-i+1); for(int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i]; for(int i=L;i<=R;i++){ int tx=ltop[i-1],ty=rtop[i+1],px=i,py=i,len=n-1; ans=1ll*a[i]*(n-1); while(tx||ty){ if(!ty||(tx&&check(rstc[i+1][ty],lstc[i-1][tx]))){ int xr=px-1,xl=xr-lstc[i-1][tx].second+1; len-=lstc[i-1][tx].second; // cerr<<"Left "<<xl<<' '<<xr<<' '<<len<<'\n'; ans+=(sum[xr]-sum[xl-1])*len+c1[xl]-c1[xr+1]-(sum[xr]-sum[xl-1])*(xl-1); px-=lstc[i-1][tx].second; tx--; } else{ int xl=py+1,xr=xl+rstc[i+1][ty].second-1; len-=rstc[i+1][ty].second; // cerr<<"Right "<<xl<<' '<<xr<<' '<<len<<'\n'; ans+=(sum[xr]-sum[xl-1])*len+c2[xr]-c2[xl-1]-(sum[xr]-sum[xl-1])*(n-xr); py+=rstc[i+1][ty].second; ty--; } } printf("%lld%c",ans,i==R?'\n':' '); } return 0; }
鲜花
初中组 T2 放过 $O(n^2\log n)$,T3 边权随机并放过小常数 $O(n^2+qn)$,水老师实乃良心出题人!!!