2021牛客暑期多校训练营5
文章目录
B:Boxes
解释
贪心可知,最多使用一次C,多次使用效果一样,早使用不比晚使用差。优先开权值小的更优。
对于每一个箱子的期望,只有它后面是全黑或者全白时,才没有贡献。
所以答案为 ∑ i = 1 n w i × ( 1 − 1 2 n − i ) \sum_{i=1}^{n}w_i\times (1-\frac{1}{2^{n-i}}) ∑i=1nwi×(1−2n−i1)
代码
int n;
double c,sum = 0; cin>>n>>c;
for(int i=1;i<=n;i++) cin>>w[i],sum += w[i];
sort(w+1,w+n+1);
p[0] = 1;
for(int i=1;i<=n;i++) p[i] = p[i-1]*0.5;
for(int i=0;i<n;i++) ans += (1.0-2*p[n-i])*w[i+1];
cout<<fixed<<setprecision(6)<<min(ans+c,sum)<<endl;
D:Double_Strings
解释
题目得出: 一段相同的前缀 + 一个不同字符(a比b小)+ 长度相同的任意后缀 就是合法方案。
可以用dp求得前面,中间通过枚举,后面可以用组合数的方法解决。
前面的问题转换为求公共子序列的个数
d p ( i , j ) dp(i,j) dp(i,j) 表示a串前i个和b串前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)
当 s ( i ) = = s ( j ) s(i) == s(j) s(i)==s(j) 时
d p ( i , j ) + = d p ( i − 1 , j − 1 ) + 1 dp(i,j) += dp(i-1,j-1) + 1 dp(i,j)+=dp(i−1,j−1)+1
后面问题
假设有a串此时还剩下x个,b串还剩下y个。 令 x <= y,则答案为 ∑ l e n = 0 x C x l e n × C y l e n = ∑ l e n = 0 x C x x − l e n × C y l e n = ∑ l e n = 0 x C x + y x \sum_{len=0}^{x}C_{x}^{len}\times C_{y}^{len} = \sum_{len=0}^{x}C_{x}^{x-len}\times C_{y}^{len} = \sum_{len=0}^{x}C_{x+y}^{x} ∑len=0xCxlen×Cylen=∑len=0xCxx−len×Cylen=∑len=0xCx+yx
代码
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#include<bits/stdc++.h>
using namespace std;
const int N = 5e3 + 10;
const int mod = 1e9 + 7;
#define int long long
typedef pair<int,int> pii;
const int INF = 1e18;
int dp[N][N];
int fact[N*2],infact[N*2];
int qmi(int a,int b){
int res = 1;
while(b){
if(b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
void init(int n,int m){
fact[0] = infact[n+m] = 1;
for(int i=1;i<=n+m;i++) fact[i] = fact[i-1] * i % mod;
infact[n+m] = qmi(fact[n+m],mod-2);
for(int i=n+m-1;i>=0;i--) infact[i] = infact[i+1] * (i+1) % mod;
}
int C(int n,int m){
return fact[n] * infact[m] % mod * infact[n-m] % mod;
}
signed main(){
IOS
string a,b; cin>>a>>b;
int n = (int)a.size(),m = (int)b.size();
init(n,m);
for(int i=0;i<=max(n,m);i++) dp[i][0] = dp[0][i] = 1;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
dp[i][j] = (dp[i-1][j] + dp[i][j-1] - dp[i-1][j-1] + mod) % mod;
if(a[i-1] == b[j-1]) dp[i][j] = (dp[i][j] + dp[i-1][j-1]) % mod;
}
int ans = 0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(a[i-1] < b[j-1]) ans = (ans + dp[i-1][j-1] * C(n+m-i-j,n-i) % mod) % mod;
cout<<ans<<endl;
return 0;
}
H:Holding_Two
解释
10101010
10101010
01010101
01010101
如上构造,合法
代码
int n,m; cin>>n>>m;
string s = "",p = "";
for(int i=0;i<m;i++){
if(i&1) s += '1',p += '0';
else s += '0',p += '1';
}
for(int i=0;i<n;i++){
if((i/2)&1) cout<<s<<endl;
else cout<<p<<endl;
}
J:Jewels
解释
每一秒都会拿一个宝珠,在0 ~ n-1秒能够全部拿完,然后每一个宝珠在0 ~ n-1秒都会对应一个位置。
用二分图带权最小匹配可以得到答案。用BFS版本的KM算法。
代码
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#include<bits/stdc++.h>
using namespace std;
const int N = 3e2 + 10;
const int mod = 998244353;
#define int long long
typedef pair<int,int> pii;
const int INF = 1e18;
int w[N][N],match[N],n;
int la[N],lb[N];//左、右部点的顶标
bool va[N],vb[N];//访问标记,是否在交错树中
int delta,upd[N],p[N],c[N];
void bfs(int x){
int a,y = 0,y1 = 0;
for(int i=1;i<=n;++i) p[i] = 0,c[i] = INF;
match[y] = x;
do{
a = match[y], delta = INF, vb[y] = true;
for(int b=1;b<=n;++b){
if(!vb[b]){
if(c[b] > la[a] + lb[b] - w[a][b])
c[b] = la[a] + lb[b] - w[a][b], p[b] = y;
if(c[b] < delta)// 还是取最小的
delta = c[b], y1 = b;
}
}
for(int b = 0; b <= n; ++ b)
if(vb[b]) la[match[b]] -= delta,lb[b] += delta;
else c[b] -= delta;
y = y1;
}while(match[y]);
while(y)match[y] = match[p[y]], y = p[y];
}
int KM(){
for(int i=1;i<=n;++i) match[i] = la[i] = lb[i] = 0;
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j) vb[j] = false;
bfs(i);
}
int res = 0; //若匹配失败w[match[y]][y]=INF;
for(int y=1;y<=n;++y) res += w[match[y]][y];
return res;
}
int distance(int x,int y,int z){
return x*x + y*y + z*z;
}
signed main(){
IOS
cin>>n;
for(int i=1;i<=n;i++){
int x,y,z,v; cin>>x>>y>>z>>v;
for(int j=1;j<=n;j++)
w[i][j] = -(distance(x,y,z+v*(j-1)));
}
cout<<-KM()<<endl;
return 0;
}
K:King_of_Range
解释
求数组中有多少对 (l,r) 使得l到r中的元素,满足最大值-最小值 > k。
双指针+单调队列
枚举r,维护单调递增序列和单调递减序列。这样可以得到以r为右端点,l为左端点时,区间最大值和最小值,此时产生的贡献为 n − r + 1 n-r+1 n−r+1 当有满足的时候,左端点往右移。
代码
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#include<bits/stdc++.h>
using namespace std;
//#define int long long
const int N = 1e5 + 10,M = 300;
typedef long long ll;
int a[N];
signed main(){
IOS
int n,m; cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=m;i++){
int k; cin>>k;
ll ans = 0;
deque<int> big,small;
for(int r=1,l=1;r<=n;r++){
while(big.size() && a[big.back()] <= a[r]) big.pop_back();
big.push_back(r);
while(small.size() && a[small.back()] >= a[r]) small.pop_back();
small.push_back(r);
while(big.size() && small.size() && a[big.front()] - a[small.front()] > k){
ans += n - r + 1;
if(big.front() <= l) big.pop_front();
if(small.front() <= l) small.pop_front();
l ++;
}
}
cout<<ans<<endl;
}
return 0;
}