【题解】牛客小白月赛25
感想:有一说一这次的小白月赛还真的挺舒服的了,代码量小,而且大部分题如果做过类似的题其实很容易就想出来怎么做了,除了B感觉是一个组合数学不是那么好想(数学太差啦),感觉这套题对小白而言是一套挺有意思的题,喜欢这套题。
A AOE还是单体?
三分的模板题,用三分枚举使用多少次第二个技能,然后输出最后的最优值即可。
代码:
#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
#include<cstring>
#include<queue>
#define fs first
#define se second
#define pb push_back
#define cppio ios::sync_with_stdio(false);cin.tie(0)
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef vector<int> VI;
const int maxn=2e5+6;
const ll inf=0x3f3f3f3f;
const ll mod=1e9+7;
ll a[maxn];
int n,x;
ll val(ll mid){
ll sum=mid*x;
for(int i=1;i<=n;i++){
if(a[i]>mid) sum+=a[i]-mid;
}
return sum;
}
int main(){
scanf("%d%d",&n,&x);
ll sum1=0,sum2=0;
ll mx=1e18;
for(int i=1;i<=n;i++){
scanf("%lld",a+i);
sum1+=a[i];
mx=min(mx,a[i]);
}
ll l=0,r=1e9;
while(l<r){
ll lmid=(2*l+r)/3,rmid=(2*r+l+2)/3;
if(val(lmid)<val(rmid)){
r=rmid-1;
}
else l=lmid+1;
}
printf("%lld",min(sum1,val(l)));
return 0;
}B k-size字符串
有空去想了,就是一个组合数学题,是小球放到盒子那种类型的。
对于k,我们可以先放好这个k,也就是说:可以先abab……,baba……,相当于把盒子放好,让你往里面填小球。
那么对于k是奇偶分为两种情况:(因为奇数除2向下取整)
k是奇数:
k是偶数: 是,b个剩余的球放到a个盒子里面。
那怎么算呢?
我们知道如果盒子不为空可以用隔板法来算,那么同理,现在盒子为空,我们就默认盒子都有一个球,那就是一共有n+m个球,有n+m-1个空隙,插入n-1个板隔开,所以答案就是 。
#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
#include<cstring>
#include<stack>
#define fs first
#define se second
#define pb push_back
#define cppio ios::sync_with_stdio(false);cin.tie(0)
#define eps 1e-7
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef vector<int> VI;
const int maxn=1e6+6;
const ll inf=0x3f3f3f3f;
const ll mod=1e9+7;
ll qPow(ll a,ll b){
ll ans=1;
while(b>0){
if(b&1) ans=(ans*a)%mod;
a=(a*a)%mod;
b>>=1;
}
return ans;
}
ll C(ll a,ll b){
ll ans=1;
for(int i=1;i<=b;i++){
ans=(ans*(a-i+1)%mod*qPow(i,mod-2))%mod;
}
return ans;
}
ll CC(ll a,ll b){
if(a<=0||b<0) return 0;
return C(a+b-1,a-1);
}
int main(){
ll n,m,k;
scanf("%lld%lld%lld",&n,&m,&k);
if(k&1){
printf("%lld",(CC(k/2+1,n-k/2-1)*CC(k/2,m-k/2)+CC(k/2,n-k/2)*CC(k/2+1,m-k/2-1))%mod);
}
else{
printf("%lld",2*CC(k/2,n-k/2)*CC(k/2,m-k/2)%mod);
}
return 0;
}C 白魔法师
树上DP,dp[i][0]表示整个以i为根子树一次都没有用过技能能得到的连通值,dp[i][1]表示整个以i为根子树使用了一次技能能得到的连通值。
然后根据当前点是W还是B来分类讨论一下:
如果当前点是W,那么:
更新完dp[i][0]后
其中mx为dp[to][1]-dp[to][0]里面的最大值,因为只能用一次技能,所以要找到自己孩子子树里面用一次技能获得收益最多的点。
如果当前点是B,那么:
代码:
#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
#include<cstring>
#include<stack>
#define fs first
#define se second
#define pb push_back
#define cppio ios::sync_with_stdio(false);cin.tie(0)
#define eps 1e-7
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef vector<int> VI;
const int maxn=1e6+6;
const ll inf=0x3f3f3f3f;
const ll mod=1e9+7;
VI edge[maxn];
char s[maxn];
int dp[maxn][2];
void dfs(int u,int fa){
int mx=0;
for(int i=0;i<edge[u].size();i++){
int to=edge[u][i];
if(to==fa) continue;
dfs(to,u);
if(s[u]=='B'){
dp[u][1]+=dp[to][0];
}
else{
mx=max(dp[to][1]-dp[to][0],mx);
dp[u][0]+=dp[to][0];
}
}
if(s[u]=='B'){
dp[u][0]=0;
dp[u][1]++;
}
else{
dp[u][0]++;
dp[u][1]=dp[u][0]+mx;
}
}
int main(){
int n;
scanf("%d",&n);
scanf("%s",s+1);
for(int i=1;i<n;i++){
int x,y;
scanf("%d%d",&x,&y);
edge[x].pb(y);
edge[y].pb(x);
}
dfs(1,0);
int ans=0;
for(int i=1;i<=n;i++){
ans=max(ans,dp[i][0]);
ans=max(ans,dp[i][1]);
}
printf("%d",ans);
return 0;
}D 抽卡
逆元应用题,因为只需要抽到一张或以上就行,那么概率就是100%减去一张都抽不到的概率。
因为要取模,所以只需要把下面的除数用费马小定理,用快速幂qPow(x,mod-2)就好了。
代码
#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
#include<cstring>
#include<stack>
#define fs first
#define se second
#define pb push_back
#define cppio ios::sync_with_stdio(false);cin.tie(0)
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef vector<int> VI;
const int maxn=1e6+6;
const ll inf=0x3f3f3f3f;
const ll mod=1e9+7;
ll qPow(ll a,ll b){
ll ans=1;
while(b>0){
if(b&1) ans=(ans*a)%mod;
a=(a*a)%mod;
b>>=1;
}
return ans;
}
ll a[maxn],b[maxn];
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%lld",a+i);
}
for(int i=1;i<=n;i++){
scanf("%lld",b+i);
}
ll tmp1=1,tmp2=1;
for(int i=1;i<=n;i++){
tmp1=(tmp1*(a[i]-b[i]))%mod;
tmp2=(tmp2*a[i])%mod;
}
printf("%lld\n",(1-tmp1*qPow(tmp2,mod-2)%mod+mod)%mod);
return 0;
}E 点击消除
栈的模板题,就用栈模拟一下,从左往右扫一遍字符串,如果栈顶和当前枚举到的字符相同就把pop栈顶,否则就把字符存入栈中,最后把栈内元素倒出来逆序输出就好了。
代码
#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
#include<cstring>
#include<stack>
#define fs first
#define se second
#define pb push_back
#define cppio ios::sync_with_stdio(false);cin.tie(0)
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef vector<int> VI;
const int maxn=3e5+6;
const ll inf=0x3f3f3f3f;
const ll mod=1e9+7;
string s;
int main(){
cppio;
cin>>s;
int n=s.length();
stack<char> sta;
for(int i=0;i<n;i++){
if(!sta.empty()){
if(sta.top()==s[i]) sta.pop();
else sta.push(s[i]);
}
else{
sta.push(s[i]);
}
}
if(sta.empty()) cout<<0;
string ss;
while(!sta.empty()){
ss+=sta.top();
sta.pop();
}
reverse(ss.begin(),ss.end());
cout<<ss;
return 0;
}F 疯狂的自我检索者
最小平均分就是未知的都是1分,最大平均分就是未知的都是5分。
答案就是:
代码:
#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
#include<cstring>
#include<queue>
#define fs first
#define se second
#define pb push_back
#define cppio ios::sync_with_stdio(false);cin.tie(0)
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef vector<int> VI;
const int maxn=1e3+6;
const ll inf=0x3f3f3f3f;
const ll mod=1e9+7;
int main(){
int n,m;
scanf("%d%d",&n,&m);
double sum=0;
for(int i=1;i<=n-m;i++){
double x;
scanf("%lf",&x);
sum+=x;
}
printf("%.5lf ",(sum+m*1)/n);
printf("%.5lf ",(sum+m*5)/n);
return 0;
}G 解方程
因为方程
可以写成
因为 是单调的,
也是单调的,所以f(x)就是一个单调函数,所以可以用二分来找到解。
但是要用long double,因为1e-7这个精度太高了,double没有这么高的精度。
代码:
#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
#include<cstring>
#include<stack>
#define fs first
#define se second
#define pb push_back
#define cppio ios::sync_with_stdio(false);cin.tie(0)
#define eps 1e-7
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef vector<int> VI;
const int maxn=1e6+6;
const ll inf=0x3f3f3f3f;
const ll mod=1e9+7;
int main(){
// printf("%lf\n",exp(1));
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
long double l=0,r=1e9;
while(r-l>eps){
long double mid=(r+l)/2;
long double tmp=pow(mid,a)+b*log(mid);
if(tmp<c) l=mid;
else r=mid;
}
printf("%.7Lf\n",l);
return 0;
}H 神奇的字母(二)
模拟一下算一下出现次数最多的字母就好了。
代码:
#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
#include<cstring>
#include<queue>
#define fs first
#define se second
#define pb push_back
#define cppio ios::sync_with_stdio(false);cin.tie(0)
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef vector<int> VI;
const int maxn=1e3+6;
const ll inf=0x3f3f3f3f;
const ll mod=1e9+7;
char s[maxn];
int num[maxn];
int main(){
int mx=0;
while(gets(s)){
int n=strlen(s);
if(n==0) break;
for(int i=0;i<n;i++){
num[s[i]-'a']++;
if(num[mx]<num[s[i]-'a']) mx=s[i]-'a';
}
}
printf("%c",'a'+mx);
return 0;
}I 十字爆破
先算一下二维前缀和,然后对于每个点的答案就是
也就是取一行和取一列然后去掉重复取的那个点。
代码:
#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
#include<cstring>
#include<stack>
#define fs first
#define se second
#define pb push_back
#define cppio ios::sync_with_stdio(false);cin.tie(0)
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef vector<int> VI;
const int maxn=1e6+6;
const ll inf=0x3f3f3f3f;
const ll mod=1e9+7;
template<class T>inline void read(T &res)
{
char c;T flag=1;
while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;res=c-'0';
while((c=getchar())>='0'&&c<='9')res=res*10+c-'0';res*=flag;
}
vector<ll> mp[maxn];
vector<ll> sum[maxn];
int main(){
int n,m;
scanf("%d%d",&n,&m);
sum[0].resize(m+2,0);
for(int i=1;i<=n;i++){
mp[i].resize(m+20,0);
sum[i].resize(m+20,0);
for(int j=1;j<=m;j++){
scanf("%lld",&mp[i][j]);
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
sum[i][j]=sum[i][j-1]+sum[i-1][j]-sum[i-1][j-1]+mp[i][j];
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
ll summ=sum[n][j]-sum[n][j-1]+sum[i][m]-sum[i-1][m];
printf("%lld%c",summ-mp[i][j]," \n"[j==m]);
}
}
return 0;
}J 异或和之和
一般这种二进制求和的题很多都是求每一位的贡献的,这题当然也不例外了。
先算在所有数里面,每一位为1出现多少次,为0出现多少次。
然后每一位的贡献就是:
也就是:如果这一位是1,那么如果要有贡献,其他两个数要么都是1要么都是0,对于其他两个数都是0,只需要用组合数C(0的数量,2)就好,对于其他两个数都是1,我们就算3个都是1有多少种情况,也就是C(1的数量,3)。
最后把贡献加起来就好了。
代码:
#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
#include<cstring>
#include<stack>
#define fs first
#define se second
#define pb push_back
#define cppio ios::sync_with_stdio(false);cin.tie(0)
#define eps 1e-7
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef vector<int> VI;
const int maxn=1e6+6;
const ll inf=0x3f3f3f3f;
const ll mod=1e9+7;
ll num[100][2];
ll qPow(ll a,ll b){
ll ans=1;
while(b>0){
if(b&1) ans=(ans*a)%mod;
a=(a*a)%mod;
b>>=1;
}
return ans;
}
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
ll x;scanf("%lld",&x);
for(int j=0;j<62;j++){
if((1ll<<j)&x){
num[j][1]++;
}
else num[j][0]++;
}
}
ll sum=0;
for(int i=0;i<62;i++){
ll tmp=0;ll one=num[i][1],zero=num[i][0];ll k=(1ll<<i)%mod;
tmp=(tmp+k*one%mod*zero%mod*(zero-1)%mod*qPow(2,mod-2))%mod;
tmp=(tmp+k*one%mod*(one-1)%mod*(one-2)%mod*qPow(6,mod-2)%mod)%mod;
sum=(sum+tmp)%mod;
}
printf("%lld\n",sum);
return 0;
}
查看3道真题和解析
