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

B.Boxes

题意:

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

思路:

题目的盒子我们考虑一串长度为01的 s s s串(0:白球 1:黑球)
f ( i ) f(i) f(i)为在第i个以后(包括i)全是0或全是1,的概率.
那么前i-1个可以是任意是排序。有人就会问到那我 s i − 1 s_{i-1} si1 0 0 0, s i s_i si后面也全是 0 0 0那不是会重复的计算嘛。hmm 换种想法思考, s i − 1 s_{i-1} si1 0 0 0,那我后面就全是 1 1 1咯,同理 s i − 1 s_{i-1} si1 1 1 1,那我后面就全是 0 0 0咯.所以 f ( i ) f(i) f(i)的总可能数是 2 i − 1 2^{i-1} 2i1.
所以这种情况概率为: f ( i ) = 2 i − 1 2 n = 1 2 n − i + 1 ( i ∈ [ 1 , n ] ) \large f(i)=\frac{2^{i-1}}{2^n}= \frac{1}{2^{n-i+1}}(i\in[1,n]) f(i)=2n2i1=2ni+11(i[1,n])
那么期望值 h o p e hope hope就是: h o p e = ∑ i = 1 n ( s u m [ i ] ∗ f ( i ) ) hope=\sum^{n}_{i=1}(sum[i]*f(i)) hope=i=1n(sum[i]f(i)). (ps: s u m [ i ] sum[i] sum[i] 为前i个盒子的价值和)
当然我们我们开箱肯定希望先开价值小,再开价值大的,盒子价值排个序。
c c c操作很特殊。这一步操作后,得知了白球,黑球的数量。
但什么情况可以不操作c呢? 即是 h o p e > s u m [ n ] 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

题意

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

思路

d p [ i ] [ j ] dp[i][j] dp[i][j] 表示 s 1 前 i 个 , s 2 前 j 个 中 共 有 多 少 个 相 同 的 子 序 列 数 目 s1前i个,s2前j个中共有多少个相同的子序列数目 s1i,s2j
如果 s 1 [ i ] < s 2 [ j ] s1[i]<s2[j] s1[i]<s2[j]那么 d p [ i − 1 ] [ j − 1 ] ∗ C ( l e n 1 − i + l e n 2 − j , l e n 1 − i ) dp[i-1][j-1]*C(len1-i+len2-j,len1-i) dp[i1][j1]C(len1i+len2j,len1i)
长度相同的任意后缀怎么得来的呢: X = l e n 1 − i Y = l e n 2 − j , 不 失 一 般 性 设 X < Y X=len1-i Y=len2-j,不失一般性设X<Y X=len1iY=len2j,X<Y
得到: ∑ i = 0 X ( C ( X , i ) ∗ ( Y , i ) ) = ∑ i = 0 X ( C ( X , X − i ) ∗ ( 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) i=0X(C(X,i)(Y,i))=i=0X(C(X,Xi)(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 >k

思路:

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

代码如下(详解)

#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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

axtices

谢谢您的打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值