2020年蓝桥杯软件类省赛 C/C++ B组 解析
补一篇
A - 跑步训练
按秒记,跑步时每秒 \(hp-10\),休息时每秒 \(hp+5\),处理到某一时刻 \(hp=0\) 即可
答案 3880
void solve()
{
int hp=10000;
int rev=0,sec=0;
int ans=0;
while(hp>0)
{
ans++;
if(rev==0)
hp-=10;
else
hp+=5;
sec++;
if(sec==60)
{
sec=0;
rev^=1;
}
}
cout<<ans<<'\n';
}
B - 纪念日
处理出天数后直接乘 \(24*60\) 得出分钟数即可,注意闰年的处理
答案 52038720
void solve()
{
int t[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
int y=1921,m=7,d=23;
int days=0;
while(y!=2020||m!=7||d!=1)
{
days++;
d++;
if(d>t[m])
{
d=1;
m++;
}
if(m>12)
{
m=1;
y++;
if(y%4==0&&y%100!=0||y%400==0)
t[2]=29;
else
t[2]=28;
}
}
cout<<days*24LL*60<<'\n';
}
C - 合并检测
不想推公式就用笨方法吧
对于每个 \(k=1\sim 100\) ,采用随机化或者大规模数据的方式多跑几次取平均值作为答案
下方代码采用的是大规模数据的方式,取 \(i\bmod 100=0\) 作为得病的人,跑出平均每人检测多少次
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
double getAns(int k)
{
int n=50000-50000%k;
int sum=0,j=0,m=0;
rep(i,1,n)
{
if(i%100==0)
j++;
if(++m==k)
{
if(j==0)
sum++;
else
sum+=k+1;
m=j=0;
}
}
return 1.0*sum/n;
}
void solve()
{
cout<<"k=1 ans=1.0"<<'\n';
rep(k,2,100)
cout<<"k="<<k<<" ans="<<getAns(k)<<'\n';
}
得出的结果如下
k=1 ans=1.0
k=2 ans=0.52
k=3 ans=0.363275
k=4 ans=0.29
k=5 ans=0.25
k=6 ans=0.226549
k=7 ans=0.212726
k=8 ans=0.205
k=9 ans=0.20094
k=10 ans=0.2
k=11 ans=0.2007
k=12 ans=0.203112
k=13 ans=0.206668
k=14 ans=0.211165
k=15 ans=0.216382
k=16 ans=0.2225
k=17 ans=0.228494
k=18 ans=0.235246
k=19 ans=0.242293
k=20 ans=0.25
...
易得 \(k=10\) 为最优解
答案 10
D - REPEAT 程序
观察输入的文件,发现每行数值不超过 \(9\),且 REPEAT
最多只有三层,因此答案不会太大
因此按行读入,取行前空格数 \(/4\) 作为层数,使用栈来存每层代表的 REPEAT
次数,借助栈来维护 A=A+...
语句的执行次数,直接按顺序处理即可
答案 241830
void solve()
{
int A=0,B=1;
string s;
stack<int> sk;
while(getline(cin,s))
{
int blank=0;
while(s[0]==' ')
{
blank++;
s=s.substr(1);
}
blank/=4;
while(sk.size()>blank)
{
B/=sk.top();
sk.pop();
}
if(s[0]=='A')
{
int n=s[8]-'0';
A+=n*B;
}
else
{
int n=s[7]-'0';
B*=n;
sk.push(n);
}
}
cout<<A<<'\n';
}
E - 矩阵
记 \(dp[i][j]\) 表示第一行放了 \(i\) 个数,第二行放了 \(j\) 个数的方案数
对于放的是什么数并不重要
因为同一行中右边的要比左边大,因此可以按从左到右的顺序处理
又因为同一列中下边的要比上边的大,因此第二行放的个数不能超过第一行,即任何时刻都需要满足 \(i\ge j\)
那么按照顺序放 \(1\sim 2020\) 这些数,每次只有 \(dp[i-1][j]\)(下一个数放第一行)与 \(dp[i][j-1]\)(下一个数放第二行)这两种状态可以转移至 \(dp[i][j]\)
答案 1340
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
ll dp[1015][1015];
void solve()
{
rep(i,1,1010)
{
dp[i][0]=1;
rep(j,1,i)
{
dp[i][j]=dp[i][j-1];
if(i<j)
{
dp[i][j]+=dp[i-1][j];
dp[i][j]%=mod;
}
}
}
cout<<dp[1010][1010];
}
F - 整除序列
注意数据范围
void solve()
{
ll n;
cin>>n;
while(n>1)
{
cout<<n<<' ';
n>>=1;
}
cout<<n<<'\n';
}
G - 解码
void solve()
{
string s;
cin>>s;
char pre;
for(char c:s)
{
if(c>='0'&&c<='9')
{
int t=c-'0'-1;
while(t--)
cout<<pre;
}
else
{
cout<<c;
pre=c;
}
}
}
H - 走方格
基础的DP,在 \(n\bmod 2+m\bmod 2=0\) 时不转移即可
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
ll dp[35][35];
void solve()
{
int n,m;
cin>>n>>m;
dp[1][1]=1;
rep(i,1,n)
rep(j,1,m)
{
if(i==1&&j==1)
continue;
if(i%2+j%2==0)
continue;
if(i-1>0)
dp[i][j]+=dp[i-1][j];
if(j-1>0)
dp[i][j]+=dp[i][j-1];
}
cout<<dp[n][m]<<'\n';
}
I - 整数拼接
考虑将数字 \(b\) 拼接在数字 \(a\) 后这一操作对于结果 \(\bmod k\) 的值的影响
记 \(b\) 的数位数量为 \(len_b\),则答案也就是 \((a\times 10^{len_b}+b)\bmod k\)
由于输入的数值范围最高 \(10^9\),有 \(10\) 个数位,因此可以预处理每个数乘上 \(10\) 的 \(1\sim 10\) 幂次后 \(\bmod k\) 的值,存储在 \(map\) 内
即记 \(map[i][j]\) 表示有多少个数 \(\times 10^i\) 之后 \(\bmod k=j\)
那么最后枚举一遍每个数 \(A_i\),即其数位数量为 \(L_i\),那也就是找有多少个数 \(\times 10^{L_i}\) 之后能与 \(A_i\) 相加为 \(K\) 的倍数,也就是找 \(\times 10^{L_i}\) 之后 \(\bmod K=(K-A_i\bmod K)\bmod K\) 的数的数量,即 \(map[L_i][(K-A_i\bmod K)\bmod K]\)
最后注意去除自身的影响
(如果是采用拼接后统计的方法做这题,注意两个 \(10^9\) 拼接得到的结果会大于 \(10^{19}\),需要使用 \(\text{unsigned long long}\))
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
int n,K;
int A[100050];
map<int,int> mp[11];
int getLen(int a)
{
int r=0;
while(a)
{
a/=10;
r++;
}
return r;
}
int pdo(int a,int b)
{
a%=K;
while(b--)
a=a*10%K;
return a;
}
void solve()
{
cin>>n>>K;
rep(i,1,n)
{
cin>>A[i];
int tmp=A[i]%K;
rep(j,1,10)
{
tmp=tmp*10%K;
mp[j][tmp]++;
}
}
ll ans=0;
rep(i,1,n)
{
int len=getLen(A[i]);
int rev=(K-A[i]%K)%K;
int t=mp[len][rev];
if(t>0&&pdo(A[i],len)==rev)
t--;
ans+=t;
}
cout<<ans<<'\n';
}
J - 网络分析
考虑并查集,每次向 \(p\) 发送信息相当于给当前 \(p\) 所在集合里的每个节点都发送一条信息,因此可以将每次发送的消息先记录在集合的根节点,最后再传递给集合的每个节点
但注意到可能一条信息发送之后,再将两个集合合并,但此前的消息是不能发送给另外一个集合内的节点的
因此考虑每次合并时往图中加点,用新节点来表示待合并的两个集合的新根,而这个新根则向原先两个集合的根连一条有向边
那么权值的传递便有了方向,每个新节点上的权值只对子树内的根节点会有影响,dfs将值传递下去即可
对于样例:
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
int gp[111111];
vector<int> G[111111];
int ans[111111];
bool vis[111111];
int fnd(int p)
{
return p==gp[p]?p:(gp[p]=fnd(gp[p]));
}
void dfs(int u)
{
for(int &v:G[u])
{
if(!vis[v])
{
vis[v]=true;
ans[v]+=ans[u];
dfs(v);
}
}
}
void solve()
{
int n;
cin>>n;
rep(i,1,n)
gp[i]=i;
int tot=n;
int m;
cin>>m;
while(m--)
{
int op;
cin>>op;
if(op==1)
{
int a,b;
cin>>a>>b;
a=fnd(a);
b=fnd(b);
if(a!=b)
{
tot++; // 新的根节点
gp[tot]=tot;
G[tot].pb(a);
G[tot].pb(b);
gp[a]=tot;
gp[b]=tot;
}
}
else
{
int p,t;
cin>>p>>t;
p=fnd(p);
ans[p]+=t; // 往根节点上做标记
}
}
per(i,tot,1) // 一定要倒着来,让新节点先搜
if(!vis[i])
{
vis[i]=true;
dfs(i);
}
rep(i,1,n)
cout<<ans[i]<<(i==n?'\n':' ');
}