哈尔滨理工大学软件与微电子学院程序设计竞赛(同步赛)题解
A.Race(模拟)
题意:
小红和小明赛跑,速度分别为$v_{1}+v_{2}$,如果小明在某个整数秒数超过小红$t$米及以上
就停下休息$s$秒,赛道长总共$l$ 米,问谁会赢
思路:
直接按照时间模拟整个过程
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<algorithm> using namespace std; int main() { int x,y,t,s,l; cin>>x>>y>>t>>s>>l; int a=0,b=0,tim=0,stop=0,flag=0; while(a<l&&b<l){ tim++; if(stop==0) a+=x,flag=0; b+=y; if(stop>0) stop--; if(a-b>=t&&!flag) stop=s,flag=1; if(a==l&&b==l) cout<<"Tie"<<" "<<tim; else if(a==l) cout<<"Ming"<<" "<<tim; else if(b==l) cout<<"Hong"<<" "<<tim; } return 0; }
B.Min Value(二分+set)
题意:
给一个数组,求$min(abs(a_{i}+a_{j})$,并求出此时的$i+j$,如果有多组最小值,输出最小的$i+j$
思路:
利用一个$set$,对于数组中的每个元素,二分离$-a[i]$最接近的两个元素,判断一下记得最小值
然后再用$map$记录一个每个元素对应的最小位置即可
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<algorithm> #include<set> #include<map> using namespace std; const int maxn=1e5+10; set<int> s; map<int,int> m; int a[maxn]; int main() { std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0); int n; cin>>n; for(int i=1;i<=n;i++){ cin>>a[i]; if(!m[a[i]]) m[a[i]]=i; } int ans1=1e9,ans2=0; s.insert(a[1]); for(int i=2;i<=n;i++){ set<int>::iterator it=s.lower_bound(-a[i]); if(it!=s.end()){ int num=*it; if(abs(a[i]+num)<ans1){ ans1=abs(a[i]+num); ans2=i+m[num]; } else if(abs(a[i]+num)==ans1) ans2=min(ans2,i+m[num]); } if(it!=s.begin()){ it--; int num=*it; if(abs(a[i]+num)<ans1){ ans1=abs(a[i]+num); ans2=i+m[num]; } else if(abs(a[i]+num)==ans1) ans2=min(ans2,i+m[num]); } s.insert(a[i]); } cout<<ans1<<" "<<ans2; return 0; }
C.Coronavirus(bfs)
题意:
$n*m$的图中有高危路段,用$*$表示,每个以高危路段为中心的九宫格都不能走
问从$S$到$E$最少需要多少步或者确定不能到达
思路:
先将周围的地方全都预处理成,再跑一遍$bfs$即可
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<algorithm> #include<queue> #include<cstdio> #include<cstring> using namespace std; const int maxn=55; char s[maxn][maxn]; int n,m,sx,sy,ex,ey,vis[maxn][maxn]; int mx[8]={0,0,1,-1,1,1,-1,-1}; int my[8]={1,-1,0,0,1,-1,1,-1}; struct node{ int x,y,num; }; void bfs() { memset(vis,0,sizeof(vis)); node now,next; now.x=sx,now.y=sy,now.num=0; vis[sx][sy]=1; queue<node> q; q.push(now); while(!q.empty()){ now=q.front(); //cout<<now.x<<" "<<now.y<<endl; q.pop(); if(now.x==ex&&now.y==ey){ cout<<now.num<<endl; return; } for(int k=0;k<4;k++){ int nx=now.x+mx[k]; int ny=now.y+my[k]; if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&s[nx][ny]!='*'&&vis[nx][ny]==0){ next.x=nx; next.y=ny; next.num=now.num+1; vis[nx][ny]=1; q.push(next); } } } cout<<"Impossible"; return; } int main() { cin>>n>>m; for(int i=1;i<=n;i++) cin>>s[i]+1; for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ if(s[i][j]=='E') ex=i,ey=j; if(s[i][j]=='S') sx=i,sy=j; if(s[i][j]=='*'&&!vis[i][j]){ for(int k=0;k<8;k++){ int nx=i+mx[k]; int ny=j+my[k]; if(s[nx][ny]=='E'||s[nx][ny]=='S'){ cout<<"Impossible"; exit(0); } if(s[nx][ny]!='*') s[nx][ny]='*',vis[nx][ny]=1; } } } } bfs(); return 0; }
D.Array(构造)
题意:
一个数组全部异或的值为$x$,全部相加为$y$,问数组最短长度为多少
思路:
原题的简化版$codeforces round 628D$
对于$a+b$,$a$^$b$可以表示本位,$a$&$b$可以表示进位,所以$y = x + 2*(a$&$b)$
所以,我们就可以发现无解的情况了,即1.$y<x$ 2.$(y-x)$%$2==1$
再来考虑$x==y$的情况,如果$y==0$,那么答案就可以为$0$,否则就为$1$
除了特殊情况外,我们来构造答案
很明显,对于普通情况,我们可以只用三个数即可,这三个数为$x,\frac{y-x}{2},\frac{y-x}{2}$这三个数
看看是否能够将答案进一步减少,我们试着将$x$跟$\frac{y-x}{2}$合并为$\frac{y+x}{2}$
所以如果$\frac{y-x}{2}$ ^ $\frac{y+x}{2}==x$那么答案就可以为2
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<algorithm> using namespace std; int main() { int x,y; while(scanf("%d%d",&x,&y)!=EOF){ if(y<x||(y-x)%2) cout<<-1<<endl; else if(x==y){ if(x==0) cout<<0<<endl; else cout<<1<<endl; } else{ if((((y-x)/2)^((y+x)/2))==x) cout<<2<<endl; else cout<<3<<endl; } } return 0; }
E.Prize(shift-and)
题意:
给n个数字,给m个匹配长度,每个匹配位置可能有多个数字满足匹配要求,问有多少个完全匹配题目给定的m个条件
思路:
$shift-and$模板题,如果会的话很容易做出来
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> bitset<2000000> a[10],dp; char s[2000005]; int main() { ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); int n,m; scanf("%d%d",&n,&m); scanf("%s",s); for(int i=0;i<n;++i) { a[s[i]-'0'][i] = 1; } dp.set(); for(int i=1;i<=m;++i) { int k; scanf("%d",&k); bitset<2000000> ndp; while(k--) { int x; scanf("%d",&x); ndp |= dp & a[x]; } dp=ndp; if(i<m) dp<<=1; } int ans=0; for(int i=0;i<n;++i) { if(dp[i]) { ++ans; i+=m-1; } } if(!ans) cout<<"Failed to win the prize"<<endl; else cout<<ans<<endl; }
F.Animal Protection(DP+单调栈)
题意:
求不包含给定点的,构成的不同矩形的数量。
思路:
用$dp[i][j]$表示以$(i,j)$为右下角矩形的数目,$suf[i][j]$表示以(i,j)为最低点该列连续$O$的数目
那么,状态转移方程式就为$dp[i][j] = dp[i][pos] + (j-pos)*suf[i][j]$,$pos$为同一行上一个$suf$值比其小的位置
如果对于每一个$j$我们都暴力去找$pos$的话肯定会超时,所以我们可以选择在这里使用单调栈来优化我们的这个$DP$转移过程
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<algorithm> using namespace std; typedef long long ll; const int maxn=1005; const int mod=1e9+7; char s[maxn][maxn]; ll suf[maxn][maxn],dp[maxn][maxn],st[maxn]; int main() { ll n,m,ans=0; cin>>n>>m; for(int i=1;i<=n;i++) cin>>s[i]+1; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){ if(s[i][j]=='X') suf[i][j]=0; else suf[i][j]=suf[i-1][j]+1; } for(int i=1;i<=n;i++){ int top=0; st[++top]=0; for(int j=1;j<=m;j++){ while(top&&suf[i][st[top]]>suf[i][j]) top--; ll num=j-st[top]; dp[i][j]=dp[i][st[top]]+num*suf[i][j]; dp[i][j]%=mod; st[++top]=j; ans=(ans+dp[i][j])%mod; } } cout<<ans; }
G.XOR(思维)
题意:
从$1-n$里面选两个数字异或最大值
思路:
假设$n$在二进制下的最高位位数为$k$
通过观察就可以发现,答案就为$2^{(k+1)} - 1$
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<algorithm> using namespace std; typedef long long ll; int main() { ll n,cnt=0,temp; cin>>n; temp=n; while(temp){ temp/=2; cnt++; } if(n==1) cout<<0; else cout<<(1ll<<cnt)-1; }
H.Maze(DFS)
题意:
$n*m$的图,图中包含$+$或者$-$
每个点能往四个方向和自己符号不同的位置走每个点能往四个方向和自己符号不同的位置走
$q$次询问,问从某个点开始最多能走向多少个不同点
思路
用$dfs$进行染色,互相可达的地方为同一颜色,并且我们记录下各个颜色块的大小
答案就为$c[i][j]$这个块内有多少点
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<algorithm> #include<cstring> using namespace std; const int maxn=3e3+10; const int MAXN=1e7+10; char s[maxn][maxn]; int c[maxn][maxn],mx[4]={0,0,1,-1},my[4]={1,-1,0,0},cnt[MAXN]; int n,m,q; void dfs(int x,int y,int num) { c[x][y]=num; cnt[num]++; for(int i=0;i<4;i++){ int nx=x+mx[i]; int ny=y+my[i]; if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&!c[nx][ny]&&s[x][y]!=s[nx][ny]) dfs(nx,ny,num); } return; } int main() { cin>>n>>m>>q; int num=1,x,y; for(int i=1;i<=n;i++) cin>>s[i]+1; memset(c,0,sizeof(c)); memset(cnt,0,sizeof(cnt)); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) if(!c[i][j]) dfs(i,j,num++); while(q--){ cin>>x>>y; cout<<cnt[c[x][y]]<<endl; } return 0; }
I.Prime(筛法求素数)
题意:
多组询问,每次求区间$[l,r]$内质数的个数
思路:
先用筛法求出$[1,10^{7}]$内所以素数,然后再求出前缀和数组即可
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<algorithm> #include<cstring> typedef long long ll; using namespace std; const int maxn=1e7+10; int num[maxn],sum[maxn],prime[maxn]; void ai_shai(){ prime[1]=1; //1不是质数 for(ll i=2;i<=10000000;++i) if(prime[i]==0) //如果是质数 for(ll j=i*i;j<=10000000;j+=i) //从i*i开始筛选 因为2*i~(i-1)*i 之前已被2~(i-1)筛出来了 prime[j]=1; //质数的倍数都不是质数 for(int i=1;i<=10000000;i++){ sum[i]=sum[i-1]; if(!prime[i]) sum[i]++; } } int main() { ai_shai(); int t,l,r; cin>>t; while(t--){ cin>>l>>r; cout<<sum[r]-sum[l-1]<<endl; } return 0; }
J.Compare(JAVA大数)
题意:
给两个数$a$跟$b$,比较大小
思路:
直接上JAVA了
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import java.math.BigInteger; import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); BigInteger a = sc.nextBigInteger(); BigInteger b = sc.nextBigInteger(); if(a.compareTo(b)>0) System.out.println(">"); else if(a.compareTo(b)==0) System.out.println("="); else System.out.println("<"); } }
K.Walk(组合数公式)
题意:
$n*m$的图,从左上角走到右下角,步数最少的情况有多少种
思路:
很明显最小步数为$n+m-2$,并且我们要向下走$m-1$步
我们可以在这$n+m-2$任意$m-1$步中选择向下走,所以答案就为$C_{n+m-2}^{m-1}$
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=2e6+5; const ll mod=1e9+7; ll invi[maxn],fac[maxn],inv[maxn]; void init() { invi[0]=invi[1]=1; fac[0]=1; inv[0]=1; for(int i=2;i<maxn;i++) invi[i]=invi[mod%i]*(ll)(mod-mod/i)%mod; for (int i=1;i<maxn;i++){ fac[i]=fac[i-1]*i%mod; inv[i]=invi[i]*inv[i-1]%mod; } } ll C(ll n,ll m) { ll ans=fac[n]*inv[m]%mod*inv[n-m]%mod; return ans; } int main() { init(); int T; scanf("%d",&T); while (T--){ ll n,m; scanf("%lld%lld",&n,&m); n--,m--; ll ans=C(n+m,m); printf("%lld\n",ans); } return 0; }
L. Defeat the monster(双指针)
题意:
给$n$个人,每个人都有能力值给$n$个人,每个人都有能力值
要组一队人探险,这一队人能力极差不能超过5要组一队人探险,这一队人能力极差不能超过$5$
问这一组最多可以有多少人问这一组最多可以有多少人
思路
将能力值从小到大排序后,用双指针遍历数组即可
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<algorithm> using namespace std; const int maxn=2e5+10; int a[maxn]; int main() { int n; cin>>n; for(int i=1;i<=n;i++) cin>>a[i]; sort(a+1,a+1+n); int ans=0,l=1,r=1,num=1; while(r<=n){ if(a[r]-a[l]<=5){ ans=max(ans,r-l+1); r++; } else l++; } cout<<ans; return 0; }