郑州大学2022年春季天梯赛省赛选拔赛暨实验室招新赛
D题是输出n的阶乘对ull自然溢出的结果。考虑到阶乘在乘的时候有很多2,所以算到某个数后2的次数达到了ull的2的数量就会永久变成0.于是等于0后直接break即可。
#include<bits/stdc++.h> using namespace std; int main() { unsigned long long now=1,n; cin>>n; for(unsigned long long i=1;i<=n;i++) { now=now*i; if(now==0) break; } cout<<now; }
A题经典蜗牛爬井。输出(h-x+x-y-1)/(x-y)+1即可。
L题是已知矩形三个点的坐标,求第四个点的坐标。我写的是先排序xi,如果x1=x2那么第四个点的x也是第三个点的x,输出x[3];否则第四个点的x是第一个点的x,输出x[1]。y同理。下来之后听说可以直接异或,非常可惜。因为如果知道有异或的话就可以用于写M题的突破口了。
#include<bits/stdc++.h> using namespace std; int x[10],y[10]; int main() { for(int i=1;i<=3;i++) cin>>x[i]>>y[i]; sort(x+1,x+1+3); sort(y+1,y+1+3); if(x[1]==x[2]) cout<<x[3]<<' '; else cout<<x[1]<<' '; if(y[1]==y[2]) cout<<y[3]; else cout<<y[1]; }
K题询问当前时刻起,下一个回文时刻是什么。我的做法是看数时刻看做高精度数加法,复原出字符串,判断是否是回文串并输出。
#include<bits/stdc++.h> using namespace std; int read() { int x; scanf("%d",&x); return x; } string ss; int h,m,s; int ask() { ss=""; ss=ss+char('0'+h/10); ss=ss+char('0'+h%10); ss=ss+char('0'+m/10); ss=ss+char('0'+m%10); ss=ss+char('0'+s/10); ss=ss+char('0'+s%10); for(int i=0;i<3;i++) if(ss[i]!=ss[5-i]) return 0; return 1; } int main() { scanf("%d:%d:%d",&h,&m,&s); while(1) { s++; if(s==60) s=0,m++; if(m==60) h++,m=0; if(h==24) h=0; if(ask()) { printf("%02d:%02d:%02d",h,m,s); return 0; } } }
B题我一开始就看到了,但是wa了一发就先写别的了。考虑枚举当前没被覆盖的左端点,给他安排一个尽量靠右的炸弹位置存在队列里,更新没被覆盖的左端点成为x*1.2+1。
#include<bits/stdc++.h> using namespace std; int read() { int x; scanf("%d",&x); return x; } int n; queue<int>q; int main() { n=read(); for(int i=1;i<=n;i++) { int x=i/0.8; if(x*4>5*i) x--; q.push(min(x,n)); i=x*1.2; } cout<<q.size()<<endl; while(q.size()) { cout<<q.front()<<' '; q.pop(); } }
C题这个公式就是求x的逆元的式子,那么答案就是L-1的阶乘除以R的阶乘=L-1的阶乘*R的阶乘的p-2次方。这个阶乘怎么做呢?我比赛的前一天恰好学习了分段打表,于是打了100个阶乘存在数组里,大段的直接取,剩下的小段循环计算。
#include<bits/stdc++.h> using namespace std; typedef long long ll; int read() { int x; scanf("%d",&x); return x; } ll p=1000000007ll; ll c[110]={1,682498929,491101308,76479948,723816384,67347853,27368307,625544428,199888908,888050723,927880474,281863274,661224977,623534362,970055531,261384175,195888993,66404266,547665832,109838563,933245637,724691727,368925948,268838846,136026497,112390913,135498044,217544623,419363534,500780548,668123525,128487469,30977140,522049725,309058615,386027524,189239124,148528617,940567523,917084264,429277690,996164327,358655417,568392357,780072518,462639908,275105629,909210595,99199382,703397904,733333339,97830135,608823837,256141983,141827977,696628828,637939935,811575797,848924691,131772368,724464507,272814771,326159309,456152084,903466878,92255682,769795511,373745190,606241871,825871994,957939114,435887178,852304035,663307737,375297772,217598709,624148346,671734977,624500515,748510389,203191898,423951674,629786193,672850561,814362881,823845496,116667533,256473217,627655552,245795606,586445753,172114298,193781724,778983779,83868974,315103615,965785236,492741665,377329025,847549272,698611116}; ll quick(ll a,ll b) { ll ans=1; for(;b;b=b/2) { if(b&1)ans=ans*a%p; a=a*a%p; } return ans; } ll ask(ll x) { ll now=c[x/10000000],y=x/10000000*10000000; for(ll i=y+1;i<=x;i++) now=now*i%p; return now; } ll l,r; int main() { cin>>l>>r; cout<<quick(ask(r),p-2)*ask(l-1)%p; }
F题是字符串数数题。回文串有一种经典的哈希判断回文串的方法。这道题的回文串只认0,1,8,我的想法是把018的一小段一小段拿出来在函数里处理。枚举每个中心,二分以他为中心的回文串长度即可。
#include<bits/stdc++.h> using namespace std; typedef long long ll; int read() { int x; scanf("%d",&x); return x; } char s[100010]; unsigned long long f[100010],f1[100010],p[100010]; string a; ll ans,mod=998244353ll; int check(int l,int r,int tl,int tr) { return (f[r]-f[l-1]*p[r-l+1])==(f1[tl]-f1[tr+1]*p[r-l+1]); } void work(int l,int r) { a="a"; for(int i=l;i<=r;i++) a=a+s[i]; int n=a.size()-1; f[0]=1; for(int i=1;i<=n;i++) f[i]=f[i-1]*131+a[i]-'0'; f1[n+1]=1; for(int i=n;i>=1;i--) f1[i]=f1[i+1]*131+a[i]-'0'; for(int i=1;i<=n;i++) { int l=0,r=min(i-1,n-i),mid; while(l+1<r) { mid=(l+r)/2; if(check(i-mid,i,i,i+mid))//101 mid=1 l=mid; else r=mid; } if(check(i-r,i,i,i+r)) ans+=r+1; else ans+=l+1; if(i!=n) { l=0,r=min(i-1,n-1-i); while(l+1<r) { mid=(l+r)/2;//00 mid=0 if(check(i-mid,i,i+1,i+mid+1)) l=mid; else r=mid; } if(check(i-r,i,i+1,i+r+1)) ans+=r+1; else if(check(i-l,i,i+1,i+l+1)) ans+=l+1; } ans=ans%mod; } } int n; int main() { n=read(); p[0]=1; for(int i=1;i<=n;i++) p[i]=p[i-1]*131; cin>>(s+1); int i=1; while(i<=n&&s[i]!='0'&&s[i]!='1'&&s[i]!='8') i++; for(;i<=n;) { int j=i; while(j<=n&&(s[j]=='0'||s[j]=='1'||s[j]=='8')) j++; work(i,j-1); i=j; while(i<=n&&s[i]!='0'&&s[i]!='1'&&s[i]!='8') i++; } cout<<ans; }
H题题意其实是(每行最大值的和)的最大值,我看了一会才看懂。考虑如果行数小于等于3的话可以无脑取前m大。如果m等于4的话则需要考虑前4大在两列,一列中的两个数相邻,另外一列中的两个数不相邻的情况。我感觉这个判断需要很多if而且很难实现,于是换了一种思路。考虑记录每一行的情况:v1[i]是第i列取一个数的最大值,v11[i]是第i列取两个相邻的数的最大值,v101[i]是第i列取两个不相邻的数的最大值,v3[i]是取3个数的最大值,v4[i]是四个数的和。于是答案是max(max(v4[i]),Σv1[i]的前4大,max(v101[i]+v101[j]),max(v11[i]+v11[j]),max(v1[i]+v3[i]))。复杂度是m的三次方。
#include<bits/stdc++.h> using namespace std; typedef long long ll; int read() { int x; scanf("%d",&x); return x; } int n,m,a[10][110],v1[110],v101[110],v11[110],v3[110],v4[110]; priority_queue<int>q; void work() { while(q.size()) q.pop(); n=read();m=read(); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) a[i][j]=read(),q.push(a[i][j]); int tt=0; for(int i=1;i<=n;i++) tt+=q.top(),q.pop(); if(n!=4)//小于4的输出前n小 { cout<<tt<<endl; return ; } tt=0; for(int i=1;i<=m;i++) { v101[i]=max(a[1][i]+a[3][i],a[2][i]+a[4][i]); v11[i]=max(max(a[1][i]+a[2][i],a[2][i]+a[3][i]),max(a[3][i]+a[4][i],a[4][i]+a[1][i])); v3[i]=a[1][i]+a[2][i]+a[3][i]+a[4][i]-min(min(a[1][i],a[2][i]),min(a[3][i],a[4][i])); v1[i]=max(max(a[1][i],a[2][i]),max(a[3][i],a[4][i])); v4[i]=a[1][i]+a[2][i]+a[3][i]+a[4][i]; } for(int i=1;i<=m;i++) { tt=max(tt,v4[i]); if(m>=2) for(int j=1;j<=m;j++) { if(i==j)continue; tt=max(tt,v1[i]+v3[j]); tt=max(tt,v101[i]+v101[j]); tt=max(tt,v11[i]+v11[j]); if(m>=3) for(int k=1;k<=m;k++) { if(i==k||j==k)continue; tt=max(tt,v1[i]+v1[k]+max(v11[j],v101[j])); } } } if(m>=4) { sort(v1+1,v1+1+m); tt=max(tt,v1[m]+v1[m-1]+v1[m-2]+v1[m-3]); } cout<<tt<<endl; } int main() { //freopen("1.in","r",stdin); for(int t=read();t;t--) work(); }
G题可以队列+模拟,从队列里取出一个点出发判断其他风场们能否到达,如果是第一次发现这个风场能到达就也放进队列里。最后从所有能到达的风场出发看能到达那些目标点。把能否到达某个风场能否到达风场以及目标点的判断函数写好就没什么问题。
#include<bits/stdc++.h> using namespace std; typedef long long ll; int read() { int x; scanf("%d",&x); return x; } struct node { int x,y,z,l; }o[2010],mu[2010]; //对于寒天之钉:z //对于风场:z是最高点,l是最低点 //对于目标:l int f[2010],flag[2010]; int n,m,vf,vd,ans; queue<int>q; int check(int x,int y) { return o[x].z>=o[y].l&&1ull*(o[x].z-o[y].l)*(o[x].z-o[y].l)*vf*vf>=1ull*(1ull*(o[x].x-o[y].x)*(o[x].x-o[y].x)+1ull*(o[x].y-o[y].y)*(o[x].y-o[y].y))*vd*vd; } int work(int x,int y) { return o[x].z>=mu[y].l&&1ull*(o[x].z-mu[y].l)*(o[x].z-mu[y].l)*vf*vf>=1ull*(1ull*(o[x].x-mu[y].x)*(o[x].x-mu[y].x)+1ull*(o[x].y-mu[y].y)*(o[x].y-mu[y].y))*vd*vd; } int main() { n=read();m=read();vf=read();vd=read(); cin>>o[0].x>>o[0].y>>o[0].z; for(int i=1;i<=n;i++) mu[i].x=read(),mu[i].y=read(),mu[i].l=read(); for(int i=1;i<=m;i++) o[i].x=read();o[i].y=read();o[i].l=read();o[i].z=read(); q.push(0); f[0]=1; while(q.size()) { int x=q.front(); q.pop(); for(int i=1;i<=m;i++) { if(f[i])continue; if(check(x,i))//x->i f[i]=1,q.push(i);//能到达的风场 } } for(int i=0;i<=m;i++) { if(f[i]) { for(int j=1;j<=n;j++) if(work(i,j))//i->j flag[j]=1; } } for(int i=1;i<=n;i++) ans+=flag[i]; cout<<ans; }
E题不带修改的话是一个经典的主席树/线段树问题。但是如今待修改并且规定初始时每种颜色棋子总数各不相同,于是最多有约140种棋子。好像复杂度还是很有问题,但是我本着均摊复杂度的思想想了想线段树+bitset,发现还是可以写的。写好之后随机数据本地跑了2s,交上去过了,只能说牛客机子就是牛,准备抄一下快读板子的也不用抄了。
#include<bits/stdc++.h> using namespace std; typedef long long ll; int read() { int x; scanf("%d",&x); return x; } bitset<145>o[10010*4],ans; int a[10010],n,b[10010],m; void build(int x,int l,int r) { if(l==r) { o[x][a[l]]=1; return ; } int mid=(l+r)/2; build(x*2,l,mid); build(x*2+1,mid+1,r); o[x]=o[x*2]|o[x*2+1]; } void ask(int x,int l,int r,int tl,int tr) { if(tl<=l&&r<=tr) { ans=ans|o[x]; return ; } int mid=(l+r)/2; if(tl<=mid) ask(x*2,l,mid,tl,tr); if(tr>mid) ask(x*2+1,mid+1,r,tl,tr); } void change(int x,int l,int r,int d,int v,int now) { if(l==r) { o[x].reset(v); o[x].set(now); return ; } int mid=(l+r)/2; if(d<=mid) change(x*2,l,mid,d,v,now); else change(x*2+1,mid+1,r,d,v,now); o[x]=o[x*2]|o[x*2+1]; } int main() { n=read();m=read(); for(int i=1;i<=n;i++) b[i]=a[i]=read(); sort(b+1,b+1+n); int sum=unique(b+1,b+1+n)-b-1; for(int i=1;i<=n;i++) a[i]=lower_bound(b+1,b+1+sum,a[i])-b; build(1,1,n); for(;m;m--) { if(read()&1) { ans.reset(); int tl=read(),tr=read(); ask(1,1,n,tl,tr); printf("%d\n",ans.count()); } else { int x=read(); change(1,1,n,x,a[x],a[x%n+1]); a[x]=a[x%n+1]; } } // cout<<clock(); }
I题抽卡游戏结论挺好想的。假如每个桶共有sum个球,前i次共抽了a个球,前i-1次抽了b个球那么在第i次出m个金球的概率=*(金球在这a个球里的概率)^m-(金球在b个球里的概率)^m。然而a可能很大,需要边加边取模才行。我在最后的半小时里绝望地提交了好多次也不知道哪里错了,根本没看出来ab会越界。
#include<bits/stdc++.h> using namespace std; typedef long long ll; ll p=998244353ll; ll quick(ll a,ll b) { ll now=1; a=a%p; while(b) { if(b&1) now=now*a%p; b=b/2; a=a*a%p; } return now; } ll n,m; int main() { scanf("%lld%lld",&n,&m); ll sum=n*(n+1)/2,a=0,b=0; ll summ=quick(sum,p-2); for(ll i=1;i<=n;i++) { a=b+n-i+1; a=a%p; printf("%lld\n",(quick(a*summ,m)-quick(b*summ,m)+2*p)%p); b=a; } }
J题是一道高斯消元的变形,考场上掏出了线性代数复习了一会之后感觉自己还是难以驾驭这道题,于是选择放弃。
M题是猜答案的题,需要你在前面的题中选择一些关键字来确定某些题的位置,剩下的题的位置再用随机数找。比如可莉的宝藏的关键字就是““No Solution”,hs爱矩形的突破点就是^。