2021牛客暑期多校(五)

B: Boxes

题目大意

n n n个盒子,每个盒子里有一个球,可能为黑色或白色且概率都为 1 2 \frac {1}{2} 21,打开一个盒子会花费 w i w_i wi,可以花费 C C C知道一共有几个黑球,求知道所有球颜色的期望花费最小是多少。 1 ≤ n ≤ 1 0 5 1≤n≤10^5 1n105 0 < C ≤ 1 0 9 0<C≤10^9 0<C109 0 < w i ≤ 1 0 9 0<w_i≤10^9 0<wi109 C C C w i w_i wi均为小数。

思路

首先如果打开所有盒子的花费比 C C C少,那么直接开所有的盒子。如果花费 C C C,那么黑球的个数可能为 1 , 2 , 3 … n 1,2,3…n 1,2,3n,则开盒子需要开到可以知道后面没开的全部是什么颜色的,也就是开出来 k k k个黑球或 n − k n-k nk个白球时停止。但这样我们依然不知道到底要开多少个,所以将问题转化为看没开的都是什么。没开的一定是连续的一串同色,所以 m m m个没开的概率就是 1 2 m \frac{1}{2^m} 2m1。开盒子一定从花费小的开始开,所以将 w i w_i wi排序后最小花费即为 C + Σ w i ∗ ( 1 – 1 2 n − i ) C+Σw_i*(1 – \frac{1}{2^{n-i}}) C+Σwi(12ni1) ( 1 – 1 2 n − i ) (1 – \frac{1}{2^{n-i}}) (12ni1)为开第 i i i个盒子期望的等比数列求和,第 n n n个盒子一定不开。

代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
double c, sum, ans, er[100010], w[100010];
int main()
{
	cin >> n >> c;
	for(int i = 1; i <= n; i++)
		scanf("%lf", &w[i]);
	sort(w+1, w+n+1);
	er[0] = 1;
	for(int i = 1; i <= 100000; i++)
		er[i] = er[i-1] / 2.0;
	ans = c;
	for(int i = 1; i <= n; i++)
	{
		sum += 1.0*w[i];
		ans += 1.0*w[i]*(1 - er[n-i]);
	}
	if(sum < ans)
		ans = sum;
	printf("%.10lf", ans);
	return 0;
}

D: Double Strings

题目大意

有两个字符串 A A A B B B,分别从这两个字符串中取出相同长度的两个子串 a a a b b b,使得存在某一位,子串 a a a上的这一位 < < < 子串 b b b上的这一位,且这一位之前两个子串相同。求方案数 m o d mod mod 1 e 9 + 7 1e9+7 1e9+7 1 ≤ ∣ A ∣ ≤ 5000 1≤∣A∣≤5000 1A5000 1 ≤ ∣ B ∣ ≤ 5000 1≤∣B∣≤5000 1B5000

思路

好的方案的构成是“一段相同的前缀 + + + 一个不同字符( a a a b b b小) + + + 长度相同的任意后缀”。 n 2 n^2 n2枚举两个字符串中的字符。 d p [ i ] [ j ] dp[i][j] dp[i][j]表示 A A A i i i B B B j j j位时前缀的方案数。转移为求公共子序列个数的方程。 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i ] [ j − 1 ] − d p [ i − 1 ] [ j − 1 ] dp[i][j] = dp[i-1][j] + dp[i][j-1] - dp[i-1][j-1] dp[i][j]=dp[i1][j]+dp[i][j1]dp[i1][j1],意义为状态 ( i , j ) (i, j) (i,j)可以由状态 ( i − 1 , j ) (i-1, j) (i1,j)或状态 ( i , j − 1 ) (i, j-1) (i,j1)在后面加上一位转移而来,但这样会导致状态 ( i − 1 , j − 1 ) (i-1, j-1) (i1,j1)被加了两次,所以要减去。如果 A [ i ] = = B [ j ] A[i] == B[j] A[i]==B[j],则 d p [ i ] [ j ] dp[i][j] dp[i][j]还要加上 d p [ i − 1 ] [ j − 1 ] + 1 dp[i-1][j-1] + 1 dp[i1][j1]+1,因为可以在之前的状态 ( i − 1 , j − 1 ) (i-1, j-1) (i1,j1)每一位后面都加上一对相同字符,且还有只有这一对相同字符的情况。计算后缀时设 a a a中此时剩余长度为 x x x b b b中剩余长度为 y y y,不失一般性地设 x ≤ y x≤y xy,现在要求的就是 Σ C ( x , i ) ∗ C ( y , i ) = Σ C ( x , x − i ) ∗ C ( y , i ) = Σ C ( x + y , x ) ΣC(x,i)*C(y,i) = ΣC(x,x-i)*C(y,i) = ΣC(x+y,x) ΣC(x,i)C(y,i)=ΣC(x,xi)C(y,i)=ΣC(x+y,x),预先处理阶乘和逆元后也可以 O ( 1 ) O(1) O(1)计算。

代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
string s1, s2;
int dp[5010][5010], mod = 1e9+7;
int pro[10010], inv[5010], ans;
int fpm(ll x, int power) 
{
    x %= mod;
    int a = 1;
    for (; power; power >>= 1, x = x*x%mod)
	    if(power & 1) a = a*x%mod;
    return a;
}
void init()
{
	pro[0] = 1;
	for(int i = 1; i <= 10000; i++)
		pro[i] = (ll)i*pro[i-1]%mod;
	inv[5000] = fpm(pro[5000], mod-2);
	for(int i = 5000; i; i--)
		inv[i-1] = (ll)inv[i]*i%mod;
	cin >> s1 >> s2;
	s1 = " " + s1;
	s2 = " " + s2;
}
int C(int n, int m)
{
	return (ll)pro[n]*inv[m]%mod*inv[n-m]%mod;
}
int main()
{
	init();
	int len1 = s1.length()-1, len2 = s2.length()-1;
	for(int i = 1; i <= len1; i++)
	{
		for(int j = 1; j <= len2; j++)
		{
			dp[i][j] = (ll)(dp[i][j-1] + dp[i-1][j] - dp[i-1][j-1])%mod;
            if(dp[i][j] < 0)
				dp[i][j] += mod;
			if(s1[i] == s2[j])
				dp[i][j] = (ll)(dp[i][j] + dp[i-1][j-1] + 1)%mod;
			if(s1[i] < s2[j])
				ans = (ll)(ans + (dp[i-1][j-1] + 1)*C(len1-i+len2-j, len1-i))%mod;
		}
	}
	printf("%d", ans);
	return 0;
}

还有一种思路,同样用 d p dp dp的思想求后缀的方案数,但是没看懂…

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
string s1, s2;
int dp[5010][5010];
int c[5010][5010], mod = 1e9+7;
int main()
{
	cin >> s1 >> s2;
	s1 = " " + s1;
	s2 = " " + s2;
	int len1 = s1.length()-1, len2 = s2.length()-1;
	for(int i = 1; i <= len1; i++)
	{
		for(int j = 1; j <= len2; j++)
		{
			dp[i][j] = (dp[i][j-1] + dp[i-1][j] - dp[i-1][j-1])%mod;
			if(dp[i][j] < 0)
				dp[i][j] += mod;
			if(s1[i] == s2[j])
				dp[i][j] = (ll)(dp[i][j] + dp[i-1][j-1] + 1)%mod;
			c[i][j] = (ll)(c[i-1][j] + c[i][j-1])%mod;
			if(s1[i] < s2[j])
				c[i][j] = (ll)(dp[i-1][j-1] + 1 + c[i][j])%mod;
		}
	}
	printf("%d", c[len1][len2]);
	return 0;
}

H: Holding Two

题目大意

构造一个 n × m n×m n×m 01 01 01矩阵,使得横竖斜没有三个连续的位置是相同的。 1 ≤ n , m ≤ 1000 1≤n,m≤1000 1n,m1000

思路

构造方式如下:
00110011 … … 00110011…… 00110011
11001100 … … 11001100…… 11001100
00110011 … … 00110011…… 00110011
11001100 … … 11001100…… 11001100
… … . ……. .

代码
#include <bits/stdc++.h>
using namespace std;
int n, m, x;
int main()
{
	scanf("%d %d", &n, &m);
	for(int i = 1; i <= n; i++)
	{
		if(i % 2 == 0)
			x = 1;
		else
			x = 0;
		for(int j = 1; j <= m; j++)
		{
			printf("%d", x);
			if(j % 2 == 0)
				x ^= 1;
		}
		printf("\n");
	}
	return 0;
}

J: Jewels

题目大意

海底有 n n n个宝石,给出每个宝石的位置 ( x i , y i , z i ) (x_i, y_i, z_i) (xi,yi,zi)和宝石下沉的速度 v i v_i vi,打捞者在 ( 0 , 0 , 0 ) (0, 0, 0) (0,0,0)处,每时刻打捞者可以花费 d 2 d^2 d2将一块宝石打捞上来, d d d为宝石和打捞者之间的距离。求将所有宝石打捞上来的最小花费。 1 ≤ n ≤ 300 1≤n≤300 1n300 0 ≤ ∣ x i ∣ , ∣ y i ∣ , z i , v i ≤ 1000 0≤∣x_i∣,∣y_i∣, z_i, v_i≤1000 0xi,yi,zi,vi1000

思路

所有宝石一定在 n − 1 n-1 n1时刻内打捞上来,每个宝石每一时刻有一个花费。那么问题可以转化为 n n n个宝石与 n n n个时刻间有连边,花费即为边权,建图后求最小权匹配即可。
D F S DFS DFS的最大权匹配是 O ( n 4 ) O(n^4) O(n4)的,如果常数足够优秀也能卡过去(比如机房 d a l a o dalao dalao),卡不过去还是老老实实写 O ( n 3 ) O(n^3) O(n3) B F S BFS BFS最大权匹配(比如我),虽然至今没看懂 B F S BFS BFS的最大权匹配是个怎么搞。

代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n, x, y, z[310], v[310];
ll lx[310], ly[310], p[310];
ll ans, w[310][310], slack[310], dlt;
int visx[310], visy[310], match[310];
void bfs(int now)
{
	int cur, cnt = 0, cntt = 0;
	for(int i = 1; i <= n; i++)
		p[i] = 0, slack[i] = 1e15;
	match[cnt] = now;
	do
	{
		cur = match[cnt];
		dlt = 1e15;
		visy[cnt] = 1;
		for(int i = 1; i <= n; i++)
		{
			if(visy[i] == 0)
			{
				if(slack[i] > lx[cur] + ly[i] - w[cur][i])
				{
					slack[i] = lx[cur] + ly[i] - w[cur][i];
					p[i] = cnt;
				}
				if(slack[i] < dlt)
				{
					dlt = slack[i];
					cntt = i;
				}
			}
		}
		for(int i = 0; i <= n; i++)
		{
			if(visy[i] != 0)
			{
				lx[match[i]] -= dlt;
				ly[i] += dlt;
			}
			else
				slack[i] -= dlt;
		}
		cnt = cntt;
	}while(match[cnt]);
	while(cnt)
	{
		match[cnt] = match[p[cnt]];
		cnt = p[cnt];
	}
}
long long km()
{
	for(int i = 1; i <= n; i++)
		match[i] = lx[i] = ly[i] = 0;
	for(int i = 1; i <= n; i++)
	{
		for(int j = 1; j <= n; j++)
			visy[j] = 0;
		bfs(i);
	}
	for(int i = 1; i <= n; i++)
		ans -= w[match[i]][i];
	return ans;
}
int main()
{
	scanf("%d", &n);
	for(int i = 1; i <= n; i++)
	{
		scanf("%d %d %d %d", &x, &y, &z[i], &v[i]);
		ans += 1ll*x*x + 1ll*y*y;
	}
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= n; j++)
			w[i][j] = -(ll)(z[i]+(j-1)*v[i])*(z[i]+(j-1)*v[i]);
	printf("%lld", km());
	return 0;
}

K: King of Range

题目大意

n n n个数 m m m次询问,对于每次询问,求出区间最大值与最小值之差严格大于 k k k的区间个数。 1 ≤ n ≤ 1 0 5 1≤n≤10^5 1n105 1 ≤ m ≤ 200 1≤m≤200 1m200 1 ≤ a i ≤ 1 0 9 1≤a_i≤10^9 1ai109 1 ≤ k ≤ 1 0 9 1≤k≤10^9 1k109

思路

每次固定右端点,将左端点不断右移,直至不再满足条件,再将右端点右移,直至满足条件。在每次左端点右移时统计答案,为当前左端点的位置 − 1 -1 1,因为左端点已经右移了。然后考虑如何 O ( 1 ) O(1) O(1)的判断当前区间是否可行。判断区间是否可行只需区间的最大值和最小值,考虑用两个单调队列维护,一个单增一个单减,每次只需用单减的队首减去单增的队首即是最大差值。左端点右移时判断是否把当前队首移出,右端点右移时入队即可。

代码
#include <bits/stdc++.h>
using namespace std;
int n, m, k, a[100010], cnt;
long long ans;
int sm[100010], sh, st;//单增维护最小 
int bg[100010], bh, bt;//单减维护最大 
int main()
{
	scanf("%d %d", &n, &m);
	for(int i = 1; i <= n; i++)
		scanf("%d", &a[i]);
	while(m--)
	{
		scanf("%d", &k);
		sh = bh = 1;
		st = bt = 0;
		bg[0] = 1e9+7;
		cnt = 1;
		ans = 0;
		for(int i = 1; i <= n; i++)
		{
			while(sm[st] > a[i] && sh <= st)
				st--;
			sm[++st] = a[i];
			while(bg[bt] < a[i] && bh <= bt)
				bt--;
			bg[++bt] = a[i];
			while(bg[bh] - sm[sh] > k)
			{
				if(sm[sh] == a[cnt])
					sh++;
				if(bg[bh] == a[cnt])
					bh++;
				cnt++;
			}
			ans += cnt - 1;
		}
		printf("%lld\n", ans);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值