2021牛客暑期多校训练营5 题解

187 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

@[TOC]

B.Boxes

题意:

nn个盒子,每个盒子里只有一个球且黑色或白色,打开盒子的需要价值为wiw_i,有一查看球数(得知当前多少个白球和黑球)的操作,需要价值为cc. 问:我要得知所有盒子的球的颜色,所需要花费的期望是多少。

思路:

题目的盒子我们考虑一串长度为01的ss串(0:白球 1:黑球) 令f(i)f(i)为在第i个以后(包括i)全是0或全是1,的概率. 那么前i-1个可以是任意是排序。有人就会问到那我si1s_{i-1}00,sis_i后面也全是00那不是会重复的计算嘛。hmm 换种想法思考,si1s_{i-1}00,那我后面就全是11咯,同理si1s_{i-1}11,那我后面就全是00咯.所以f(i)f(i)的总可能数是2i12^{i-1}. 所以这种情况概率为:f(i)=2i12n=12ni+1(i[1,n])\large f(i)=\frac{2^{i-1}}{2^n}= \frac{1}{2^{n-i+1}}(i\in[1,n]) 那么期望值hopehope就是:hope=i=1n(sum[i]f(i))hope=\sum^{n}_{i=1}(sum[i]*f(i)). (ps:sum[i]sum[i] 为前i个盒子的价值和) 当然我们我们开箱肯定希望先开价值小,再开价值大的,盒子价值排个序。 cc操作很特殊。这一步操作后,得知了白球,黑球的数量。 但什么情况可以不操作c呢? 即是hope>sum[n]hope>sum[n]时候没必要知道白黑数,我直接全开完。

代码如下:

#include<bits/stdc++.h>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e5+10;
const ll P=1e9+7;
ll read(){
    ll s = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
    return s * f;
}
using namespace std;
ll n,m;
double  w[N];
double sum[N];
double  c;
void solve(){
     n=read();
	 cin>>c;
     rep(i,1,n){
		 cin>>w[i];
//		 sum[i]=sum[i-1]+w[i];
	 } 
	 sort(w+1,w+1+n);
	 rep(i,1,n){
	 	sum[i]=sum[i-1]+w[i];
	 }
	 double ans=0;
	 double bit=1;
	 for(int i=n;i>=1;i--){
	 	// 从后往前,便于叠加(2^{n-i+1}) 
		 bit/=2;
		 ans+=sum[i-1]*bit;
	 }
     if(ans+c<=sum[n]){
		  printf("%.6lf\n",ans+c);
	 }else{
		 printf("%.6lf\n",sum[n]);
	 }
	 return ;
}
int main (){
//   freopen("in.txt","r",stdin);
//   freopen("out.txt","w",stdout);
  solve();
  return 0;
}

D.Double Strings

题意

给两个串s1,s2s1,s2,从两个串中各自选出长度相等子序列st1,st2, 问满足 "一段相同的前缀+一个不同字符(a<b)+长度相同的任意后缀"的数目。

思路

dp[i][j]dp[i][j] 表示s1i,s2j个中共有多少个相同的子序列数目s1前i个,s2前j个中共有多少个相同的子序列数目 如果 s1[i]<s2[j]s1[i]<s2[j]那么 dp[i1][j1]C(len1i+len2j,len1i)dp[i-1][j-1]*C(len1-i+len2-j,len1-i) 长度相同的任意后缀怎么得来的呢: X=len1iY=len2j,不失一般性设X<YX=len1-i Y=len2-j,不失一般性设X<Y 得到:i=0X(C(X,i)(Y,i))=i=0X(C(X,Xi)(Y,i))=C(X+Y,X)\sum^{X}_{i=0}(C(X,i)*(Y,i))=\sum^{X}_{i=0}(C(X,X-i)*(Y,i))=C(X+Y,X) 证明: 在这里插入图片描述

代码如下:

#include<bits/stdc++.h>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e5+10;
const ll P=1e9+7;
const ll MOD=1e9+7;
ll read(){
    ll s = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
    return s * f;
}
using namespace std;
char s1[5010],s2[5010];
ll bit[5010];
void init(){
	bit[0]=1;
	rep(i,1,5000){
		bit[i]=(bit[i-1]<<1)%P;
	}
	return ;
}
int Fac[10010],Inv[10010];
void Prepare(int n)
{
	Fac[0] = 1;
	for (int i = 1; i <= n; i ++)
		Fac[i] = 1LL * Fac[i - 1] * i % MOD;
	Inv[0] = Inv[1] = 1;
	for (int i = 2; i <= n; i ++)
		Inv[i] = MOD - 1LL * (MOD / i) * Inv[MOD % i] % MOD;
	for (int i = 2; i <= n; i ++)
		Inv[i] = 1LL * Inv[i - 1] * Inv[i] % MOD;
}
inline int C(int u, int v)
{
	if (u < 0 || v < 0 || u < v)
		return 0;
	return 1LL * Fac[u] * Inv[v] % MOD * Inv[u - v] % MOD;
}
ll dp[5050][5010];
void solve(){
//	 cout<<C(2,5)<<endl;
    scanf("%s%s",s1+1,s2+1);
	init();
	int len1=strlen(s1+1);
	int len2=strlen(s2+1);
	Prepare(len1+len2);
	ll ans=0;
	rep(i,0,len1){
		dp[i][0]=1;
	}
	rep(j,1,len2){
		dp[0][j]=1;
	}
    rep(i,1,len1){
		rep(j,1,len2){
			// 这里存在减法操作可能得到为负数 加个P 
			dp[i][j]=(dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1]+P)%P;
            if(s1[i]==s2[j]){
				dp[i][j]=(dp[i][j]+dp[i-1][j-1])%P;
			}else if(s1[i]<s2[j]){
				int x=len1-i;
				int y=len2-j;
				if(x>y) swap(x,y);
//				    printf("i:%d j:%d\n",i,j);
//				    printf("x:%d y:%d C:%d \n",x,y,C(x+y,x));
//				    printf("dp:%d\n",dp[i-1][j-1]);
                    ans=(ans+dp[i-1][j-1]*C(x+y,x)%MOD)%MOD;
			} 
		}
    }
//	rep(i,1,len1){
//		rep(j,1,len2){
//			printf("%d ",dp[i][j]);
//		}
//		printf("\n");
//	}
	printf("%lld\n",ans);
	return ;
}
int main (){
//  freopen("in.txt","r",stdin);
//  freopen("out.txt","w",stdout);
  solve();
  return 0;
}

K.King of Range

题意:

给一数组,问有多少个区间的极差值>k>k

思路:

对于区间[l,r][l,r],我们可以先固定右端点rr,寻找离rr最近的ll且满足区间[l,r][l,r]极差值>k>k. 于是我们用到了两个队列,分别是递增队列和递减队列。 队列存放aa数组的下标。 关于递增和递减队列,都有左右端点。两个队列的左端点必定是极差最大值。开始移动两个队列的左端点。 计算两个左端点 对应aa数组的极差值,大于kk 开始移动。该值不大于kk,那后面的所有元素极差值都不会满足,终止。

代码如下(详解)

#include<bits/stdc++.h>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e5+10;
const ll P=1e9+7;
ll read(){
    ll s = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
    return s * f;
}
using namespace std;
ll n,m;
ll a[N];
int qadd[N],qde[N];  // 递增队列 递减队列  都是存放a数组的下标 
void solve(){
	n=read();
    m=read();
    rep(i,1,n){
      scanf("%d",a+i);
	}
	while(m--){
		int k=read();
		int lde=1,rde=1;   // 递减队列 lde:左端点  rde:右端点 
		int ladd=1,radd=1;  // 递增队列  ladd:左端点  radd:右端点 
		qde[1]=1;qadd[1]=1;  // 初始化 
		ll ans=0;
		int cnt=0;
		for(int i=2;i<=n;i++){
			//首先新进来的a[i] 需添加到两队列 
		for(;lde<=rde and a[qde[rde]]<=a[i];rde--) ;
		qde[++rde] =i;
		for(;ladd<=radd and a[qadd[radd]] >= a[i];radd--);
		qadd[++radd]=i;
		//两个队列的左端点开始移动 知道两端点对应的a数组极差值<=k 就结束或者不能移动啦。 
		while(lde<=rde and ladd<=radd  and a[qde[lde]]-a[qadd[ladd]]>k){
		    // 队列存放是a数组下标,固定r,我们肯定是寻求离r最近的l
			// 判断
			if(qde[lde]<qadd[ladd]){ 
				// qde[led]为左端点 
				cnt=qde[lde];
				lde++;        //队列左端点移动 
			}else{
				//qadd[ladd]为左端点 ,另个为右端点 
				cnt=qadd[ladd];
				ladd++;
			}
		}
//		printf("i:%d cnt:%d\n",i,cnt);
		ans+=cnt;
	    }
        cout<<ans<<endl;
	}
    return ;
}
int main (){
//  freopen("in.txt","r",stdin);
//  freopen("out1.txt","w",stdout);
  solve();
  return 0;
}