写在题解前面的话
本次比赛相对难度不大(毕竟是小白月赛),没有刻意去出防ak题(因为自己太菜了出不了)。整场比赛没有难度高的算法,代码量也很小,希望给大家带来了不错的比赛体验~
这场比赛的题目与其说是考算法,不如说是考语法和程序设计中的一些其他知识更多一些,大家在学算法的时候往往会忽视一些细节问题,以后能重视起来会比较好。
E题的测试用例出弱了,放过了部分的错误算法。样例已经加强(但不会rejudge)
,大家可以重新提交试试,不影响成绩。
A
知识点:贪心
思路:显然aoe伤害打的怪物越多是越赚的,临界值是消耗 正好打 个怪物,这时aoe和单体伤害的效率是相同的。所以可以采用以下思路:先用aoe击杀到正好剩x个怪,然后剩下的全部用单体击杀即可。
私货:aoe(Area of effect),范围型技能。
标程:
#include<bits/stdc++.h> using namespace std; #define ll long long ll a[222222]; int main(){ ll n,x,i,j,k; cin>>n>>x; for(i=0;i<n;i++)cin>>a[i]; if(x>=n){ ll sum=0; for(i=0;i<n;i++)sum+=a[i]; cout<<sum; return 0; } sort(a,a+n); ll sum=a[n-x]*x; for(i=n-x+1;i<n;i++)sum+=a[i]-a[n-x]; cout<<sum; }
B
知识点:组合数学
思路:对于 是偶数和奇数的情况分别判断:
如果k是偶数,那么字符串一定是a....b或者b....a的形式。我们可以拿出 个a 以及 个b先摆好:
ababab...abab
然后还剩下的a和b往里面插入,就变成了很经典的整数划分问题: 个物品放进 个格子里,有多少多方法(或者一个整数 拆分成 k/2 个非负整数之和的拆分方式,不同位置视为不同拆法)。
设将整数 拆成 个非负整数的方案数为
假设第一个数为 ,那么这相当于把 拆成 个非负整数的方案数为
假设第一个数不是 ,那么相当于把 拆成 个非负整数的方案数为
也就是说 。
发现了什么?是不是联想到了杨辉三角,也就是对应的组合数。
我们可以列几个简单的例子,
然后假设
用数学归纳法可以证明:
那么有
故假设成立,可由 和 推至全部整数。
解决了整数划分问题,这种情况就解决了。
b....a形式同理。
如果k是奇数,那么字符串一定是a....a或者b....b的形式。解决方式和上面类似。但要注意的是这时两种计数的个数是不等的。请同学们自行思考解决。
私货:这道题应该是本场比赛中难度最大的了。之所以放在了第二个位置仅仅因为题目是按字符顺序排序的(才不是想故意卡大家做题顺序)。这道题的idea在半年前就有了,这一次终于出成了题目也算了却了一个心愿w
标程:
#include<bits/stdc++.h> using namespace std; #define ll long long const int mod=1e9+7; ll power(ll a,ll b){ ll res=1; while(b){ if(b&1)res=res*a%mod; b>>=1,a=a*a%mod; } return res; } ll inv(ll x){return power(x,mod-2);} ll C(ll a,ll b){ ll t=1; for(int i=1;i<=b;i++)t=t*(a-i+1)%mod*inv(i)%mod; return t; } ll f(ll n,ll m){ if(n<=0||m<0)return 0; return C(n+m-1,m); } int main(){ ll n,m,k; cin>>n>>m>>k; if(k&1) cout<<(f(k/2+1,n-k/2-1)*f(k/2,m-k/2)+f(k/2,n-k/2)*f(k/2+1,m-k/2-1))%mod; else cout<<2*f(k/2,n-k/2)*f(k/2,m-k/2)%mod; }
C
知识点:图论、并查集
思路:如果将一个黑色点染成白色,那么将得到一个白色连通块,这个连通块由和这个黑色点连结的所有白色连通块组成。
如果将一个白色点染成白色,那么不会有任何变化。
所以我们可以先并查集预处理一下,把所有白色连通块的大小求出来,并把所有白色点对应的连通块表示一下。连通块的大小可以dfs或者统计并查集根的孩子总数得出。
然后统计所有黑色点的邻点连通块大小即可。要注意特判全部是白色点的情况。
私货:如果做过寒假集训营(第一场)的maki和tree那道题的话,这道题做起来会相对比较轻松,因为思路类似。另外又白又膜,这种危险的出题人还是打死好了(不
(放一下寒假集训营1的链接吧,也是我出的:https://ac.nowcoder.com/acm/contest/3002 如果没有购买也可以通过点别人的代码来看题,这是牛客网的漏洞,悄悄透露给大家,清楚妹妹不要打我qwq)
代码:
#include<bits/stdc++.h> using namespace std; #define ll long long vector<ll>g[200020]; string s; ll fa[200020],kdm[200020],sz[200020]; ll f(ll x){ if(fa[x]==x)return x; return f(fa[x]); } void uni(ll x,ll y){ ll p=f(x),q=f(y); if(p!=q){ if(kdm[p]<kdm[q]){ fa[p]=q; kdm[q]+=kdm[p]+1; } else{ fa[q]=p; kdm[p]+=kdm[q]+1; } } } int main(){ ll n,i,j,k,x,y; cin>>n>>s; for(i=1;i<=n;i++)fa[i]=i; for(i=1;i<n;i++){ cin>>x>>y; g[x].push_back(y); g[y].push_back(x); if(s[x-1]=='W'&&s[y-1]=='W')uni(x,y); } for(i=1;i<=n;i++) if(s[i-1]=='W')sz[i]=kdm[f(i)]+1; ll ma=0; for(i=1;i<=n;i++){ if(s[i-1]=='B'){ ll sum=1; for(j=0;j<g[i].size();j++) if(s[g[i][j]-1]=='W')sum+=sz[g[i][j]]; ma=max(ma,sum); } } if(ma==0)cout<<n; else cout<<ma; }
D
知识点:概率论
思路:其实这题根本没有什么概率论知识,想考察大家更多的是逆元的知识(相信有很多萌新是不知道或者不会求逆元的)
概率论方面,求抽到想要的卡的概率,直接先求出全部抽完都没有想要的卡的概率,显然是
其中
然后用1减去这个概率就可以了。
这里给萌新们讲讲逆元的知识(会的大佬可以跳过了)
所谓逆元,即在模 意义下的值。设该值为 ,即 ,也就是满足 的那个a
怎么求这个数呢?其实很简单。由费马小定理知, 和 互素时,
那么
也就是说, 就是 在模 意义下的逆元了。
现在这道题要求 ,相当于求 乘以 的逆元。逆元的知识很重要,可以把分数当成整数进行各种运算。
私货:ue对不起~(>=<)
标程代码:
#include<bits/stdc++.h> using namespace std; #define ll long long const ll mod=1e9+7; ll power(ll a,ll b){ ll res=1; while(b){ if(b&1)res=res*a%mod; b>>=1; a=a*a%mod; } return res; } ll inv(ll x){return power(x,mod-2);} ll a[100010],b[100010]; int main(){ freopen("6.in","r",stdin); freopen("6.out","w",stdout); ll n,k,i,j; cin>>n; for(i=0;i<n;i++)cin>>a[i]; for(i=0;i<n;i++)cin>>b[i]; ll res=1; for(i=0;i<n;i++){ res=res*(a[i]-b[i])%mod*inv(a[i])%mod; } cout<<(mod+(1-res))%mod; }
E
知识点:字符串
思路:这道题解法很多,这里分享两种解法。
第一个解法是用栈,相对容易理解。遍历字符串,遇到相同字母出栈,否则入栈即可。
另外一个解法是,开一个标记数组去记录哪些被消除过了,再用另外一个数组记录目前存活的字符串里,每个字符的左字符位置。然后用一个变量维护消除的左端点位置,遍历时如果能消除就让这个变量左移,并同时更新两个数组的情况,否则重置到右边的位置。详细见标程代码。
标程代码:
#include<bits/stdc++.h> using namespace std; #define ll long long int lf[300030],mk[300030]; int main(){ ll n,i,j,k,x=0; string s; cin>>s; n=s.length(); for(i=0;i<n;i++)lf[i]=i-1; for(i=0;i<n-1;i++){ int l=i,r=i+1; if(s[i]==s[i+1]){ while(l>=0&&r<n&&s[l]==s[r]){ mk[l]=mk[r]=1; l=lf[l]; r++; } lf[r]=l; } i=r-1; } for(i=0;i<n;i++){ if(!mk[i])cout<<s[i],x=1; } if(!x)cout<<0; }
F
知识点:概率、贪心
签到题。假设所有隐藏的分数为1或5即可。
标程代码:
#include<bits/stdc++.h> using namespace std; #define ll long long int main(){ ll n,m; double sum=0; cin>>n>>m; n-=m; for(int i=0;i<n;i++){ double x; cin>>x; sum+=x; } printf("%.5lf %.5lf",(sum+m)/(n+m),(sum+5*m)/(n+m)); }
G
知识点:二分
很容易发现左边是一个单调增的函数,所以二分求解即可。值得注意的是如果用double可能出现tle的情况(实测double精度有问题,导致后面无限不动)。解决方法有两种,一种是换long double,另外一种是进行足够多次二分即可(例如1000次二分),可以避免tle的出现。
标程:
#include<bits/stdc++.h> using namespace std; #define ld long double int a; ld b,c; ld bs(ld l,ld r){ if(r-l<=1e-8)return l; ld mid=(r+l)/2; ld res=1; for(int i=0;i<a;i++)res*=mid; res+=b*log(mid); if(res<c)return bs(mid,r); return bs(l,mid); } int main(){ freopen("8.in","r",stdin); freopen("8.out","w",stdout); cin>>a>>b>>c; printf("%.7Lf",bs(1,c)); }
$$ H
知识点:无
思路:这道题想更多的考察大家对“若干组输入”的处理方式。主要的解决方法有以下几种:
①while(cin>>str)
②while(scanf("%s",str)!=EOF) //EOF可以用-1代替。
③while(gets(str)!=NULL)
第一个是c++写法,后两个是C语言写法。注意对应的头文件即可(或使用万能头文件)
标程代码:
#include<bits/stdc++.h> using namespace std; string s; int t[26]; int main(){ int i; while(cin>>s){ for(i=0;i<s.length();i++)t[s[i]-'a']++; } int ma=0,m1=0; for(i=0;i<26;i++){ if(ma<t[i])ma=t[i],m1=i; } cout<<char('a'+m1); }
I
知识点:预处理。
思路:开两个数组分别处理每一行的和以及每一列的和。之后对应顶点输出(对应行+对应列-该顶点)即可。
这道题更难的地方是n和m大小未定。解决方法有两种,一个是动态内存(malloc或者vector),另一种方法是用一维数组代替二维数组(a[i][j]和a[i*m+j]等价)。这里标程用的是第二种方法。
标程代码:
#include<bits/stdc++.h> using namespace std; #define ll long long ll a[1000010]; ll sr[1000010],sc[1000010]; int main(){ int n,m,i; cin>>n>>m; for(i=0;i<n*m;i++)scanf("%lld",&a[i]); for(i=0;i<n*m;i++){ sr[i/m]+=a[i]; sc[i%m]+=a[i]; } for(i=0;i<n*m;i++){ printf("%lld ",sr[i/m]+sc[i%m]-a[i]); if(i%m==m-1)printf("\n"); } }
J
知识点:位运算
思路:做这一类题有个技巧,就是每一位分别去处理,1e18对应二进制的64位。
可以先统计出每一位的1的个数和0的个数,那么异或和就可以用组合数学的方式求出来了,因为异或为1一定是:
或者
这两种情况中的一种,分别处理即可。(别忘了对应位最后乘以 )
标称代码:
#include<bits/stdc++.h> using namespace std; #define ll long long const int mod=1000000007; ll a[1000010]; ll t[64]; ll power(ll a,ll b){ ll res=1; while(b){ if(b&1)res=res*a%mod; b>>=1,a=a*a%mod; } return res; } ll inv(ll x){ return power(x,mod-2); } ll f(ll x,ll y){ if(x<y)return 0; ll res=1; for(ll i=0;i<y;i++)res=res*(x-i)%mod,res=res*inv(i+1)%mod; return res; } int main(){ ll n,m,i,j,x; cin>>n; for(i=0;i<n;i++){ cin>>x; j=0; while(x){ if(x&1)t[j]++; j++,x>>=1; } } ll sum=0; for(i=0;i<64;i++){ sum+=(1LL<<i)%mod*(f(t[i],3)+f(n-t[i],2)*t[i]%mod)%mod; sum%=mod; } cout<<sum; }