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 1≤n≤105, 0 < C ≤ 1 0 9 0<C≤10^9 0<C≤109, 0 < w i ≤ 1 0 9 0<w_i≤10^9 0<wi≤109。 C C C和 w i w_i wi均为小数。
思路
首先如果打开所有盒子的花费比 C C C少,那么直接开所有的盒子。如果花费 C C C,那么黑球的个数可能为 1 , 2 , 3 … n 1,2,3…n 1,2,3…n,则开盒子需要开到可以知道后面没开的全部是什么颜色的,也就是开出来 k k k个黑球或 n − k n-k n−k个白球时停止。但这样我们依然不知道到底要开多少个,所以将问题转化为看没开的都是什么。没开的一定是连续的一串同色,所以 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∗(1–2n−i1)。 ( 1 – 1 2 n − i ) (1 – \frac{1}{2^{n-i}}) (1–2n−i1)为开第 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 1≤∣A∣≤5000, 1 ≤ ∣ B ∣ ≤ 5000 1≤∣B∣≤5000 1≤∣B∣≤5000。
思路
好的方案的构成是“一段相同的前缀 + + + 一个不同字符( 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[i−1][j]+dp[i][j−1]−dp[i−1][j−1],意义为状态 ( i , j ) (i, j) (i,j)可以由状态 ( i − 1 , j ) (i-1, j) (i−1,j)或状态 ( i , j − 1 ) (i, j-1) (i,j−1)在后面加上一位转移而来,但这样会导致状态 ( i − 1 , j − 1 ) (i-1, j-1) (i−1,j−1)被加了两次,所以要减去。如果 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[i−1][j−1]+1,因为可以在之前的状态 ( i − 1 , j − 1 ) (i-1, j-1) (i−1,j−1)每一位后面都加上一对相同字符,且还有只有这一对相同字符的情况。计算后缀时设 a a a中此时剩余长度为 x x x, b b b中剩余长度为 y y y,不失一般性地设 x ≤ y x≤y x≤y,现在要求的就是 Σ 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,x−i)∗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 1≤n,m≤1000。
思路
构造方式如下:
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 1≤n≤300, 0 ≤ ∣ x i ∣ , ∣ y i ∣ , z i , v i ≤ 1000 0≤∣x_i∣,∣y_i∣, z_i, v_i≤1000 0≤∣xi∣,∣yi∣,zi,vi≤1000。
思路
所有宝石一定在
n
−
1
n-1
n−1时刻内打捞上来,每个宝石每一时刻有一个花费。那么问题可以转化为
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 1≤n≤105, 1 ≤ m ≤ 200 1≤m≤200 1≤m≤200, 1 ≤ a i ≤ 1 0 9 1≤a_i≤10^9 1≤ai≤109, 1 ≤ k ≤ 1 0 9 1≤k≤10^9 1≤k≤109。
思路
每次固定右端点,将左端点不断右移,直至不再满足条件,再将右端点右移,直至满足条件。在每次左端点右移时统计答案,为当前左端点的位置 − 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;
}