B. Boxes
题意:箱子里有黑球和白球,你可以花费Vi 打开箱子,花费C询问裁判黑球数量。现在求你知道箱子里球的颜色的最少期望。
思路:首先对于询问黑球个数,我们只问第一次,后面的根据所开的球的数量可以得到。然后就是找规律。我们分析四个球,现在用0表示白,用1表示黑。
所有情况2n,
第一次就知道:0000, 1111。 2/16。
第二次就知道:(0)111, (1)000。 2/16。
第三次就知道:()011, ()100。 4/16。
第四次就知道:()()01, ()()10。 8/16。
发现从第二次就是21,22,23…。可以再举例子找,我不会数学证明。
然后呢把盒子的钱排序再乘起来,加起来。最后取最小。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
double v[N];
double sum = 0;
int main(){
int n;
double x;
cin >> n >> x;
for(int i = 1; i <= n; i++){
cin >> v[i];
sum += v[i];
}
sort(v+1, v+1+n);
double ans = x;
for(int i = 1; i <= n-1; i++){
ans += (1 - pow(0.5, n-i))*v[i];
}
cout << fixed << setprecision(9) << min(ans, sum) << endl;
}
要注意的是我们 1-() 是因为我们算的是不能直接知道的概率。
D. double string
题目:两个字符串A,B。找子序列a, b。使得a < b, 并满足前面的字符相同,中间有一个不同的字符,后面随便放。
思路:小于的字符可以枚举,前缀相同要dp, 后缀是组合数学。
我懒得证明,这里来一个大佬的链接。
大佬的题解,我照着思路写的
稍微解释一下为什么当A[i] == B[i] 的时候为什么要加上一次的+1.
例如,A = “ab”, B = “ab”。这个时候相同子序列有3个,a, b, ab。
当 A = “abc", B = “abc” 时候是7个,a, b, ab, ac, bc, abc, c。也就是说当多了一个相同的之后,前面的相同的加上这个字符不会影响,还要加上多了的自己。当然,我感觉这个地方位置不同也代表子序列不同(主要是大家都是这么写的,我不太懂)。要不然对于aba, aba,就无法解释了。
真的不会dp…QwQ.
代码
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
#define int long long
const int N = 5010;
const ll mod = 1e9 + 7;
ll dp[N][N];
ll Fac[N*2], Inv[N*2];
void init(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;
}
map<pair<char, char>, int> mp;
signed main(){
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
string a, b;
cin >> a >> b;
a = " " + a; b = " " + b;
ll n = a.length(), m = b.length();
init(5002*2);
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= m; j++)
{
dp[i][j] = ( dp[i][j-1] + dp[i-1][j] - dp[i-1][j-1] + mod ) % mod;
if(a[i] == b[j])
{ dp[i][j] += dp[i-1][j-1] + 1;
dp[i][j] %= mod;
}
mp[{a[i], b[j]}]++;
}
}
ll ans = 0;
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= m; j++)
{
if(a[i] < b[j])
{
ans += (ll)(dp[i-1][j-1]+1)*C(n+m-i-j-2 , n-i-1);
ans %= mod;
}
}
}
cout << ans << endl;
}
H. hoding two
题目:构造一个只有0,1的二维数组,使得任意3*3矩阵中没有连着的3个
一样的。
思路:两个连着相同的就好。
代码:
#include<bits/stdc++.h>
using namespace std;
int main(){
int n, m;
cin >> n >> m;
vector<int> ss = {0, 1};
vector<int> s1 = {0, 1};
for(int i = 1; i <= n; i++){
int k, j, cot = 0;
if(i & 1){
k = 0, j = 0;
}else{
k = 1, j = 1;
}
while(cot <= m){
if(cot<m){
cout << ss[k%2];
k++, cot++;
}
if(cot < m){
cout << s1[j%2];
j++, cot++;
}
if(cot == m) break;
}
cout << endl;
}
}
J. Jewels
黄金矿工…
题目:你在(0,0,0), 宝物在(x,y,z)。并且还会下沉。你捞东西花(x2+y2+z2)。但是宝物会下沉。求所花费的最少。
思路:本来以为是暴力,结果是KM。套板子。把时间和宝物看成点,然后权值就是打捞的花费。然后套板子。原谅我不会KM。
代码:
#include <bits/stdc++.h>
#include <iostream>
#include <vector>
#define int long long
using namespace std;
namespace km
{
using T = long long;
const int V = 505;
int pre[V]; // 增广路中右侧点的同侧前驱
int linker[V]; // 右边点的左侧匹配点
bool vis[V]; // 是否在增广路中
T g[V][V]; // 权值图
T slack[V]; // 右侧点的松弛值
T lx[V], ly[V]; // 顶标
void bfs(int root, int n) // 增广树的根
{
memset(vis, 0, sizeof(vis));
memset(pre, 0, sizeof(pre)); // 右边同侧的前驱点
memset(slack, 0x3f, sizeof(slack)); // 右边每个点需要松弛的最小值
int v = 0;
linker[v] = root;
while (1)
{
T delta = 0x3f3f3f3f3f3f3f3f;
int u = linker[v]; // v 的匹配点
int next_v;
vis[v] = true;
for (int i = 1;i <= n;i++) // 尝试为 u 找一条边加入子图
{
if (!vis[i])
{
if (slack[i] > lx[u] + ly[i] - g[u][i]) // 更新右边每个点的最小松弛值
{
slack[i] = lx[u] + ly[i] - g[u][i];
pre[i] = v;
}
if (slack[i] < delta) // 更新全局最小松弛值. 如果slack是0说明边在里面,
delta = slack[i], next_v = i;
}
}
for (int i = 0; i <= n;i++)
{
if (vis[i]) // v已经在增广路中了
lx[linker[i]] -= delta, ly[i] += delta;
else
slack[i] -= delta;
}
v = next_v;
if (!linker[v]) // 找到了增广路
break;
}
while (v) // 增广路的边取反
linker[v] = linker[pre[v]], v = pre[v];
}
T KM(int n)//点数(需要找到多少个匹配?)
{
memset(linker, 0, sizeof(linker));
memset(lx, 0, sizeof(lx));
memset(ly, 0, sizeof(ly));
for (int i = 1;i <= n;i++)
bfs(i, n);
T res = 0;
for (int i = 1;i <= n;i++) if (linker[i])
res += g[linker[i]][i];
return res;
}
//先初始化,再加边
void init() {
for (int i = 0; i < V; i++)for (int j = 0; j < V; j++)g[i][j] = -0x3f3f3f3f3f3f3f3f;
}
void add(T u, T v, T w) {
g[u][v] = max(g[u][v], w);
}
}
const int maxn=303;
int Z[maxn], V[maxn];
int cal(int a, int b, int c){
return (a+b*c)*(a+b*c);
}
signed main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int n;
cin >> n;
int res = 0;
for(int i = 1, x, y; i <= n; i++){
cin >> x >> y >> Z[i] >> V[i];
res += x*x + y*y;
}
km::init();
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
km::add(i, j, -cal(Z[j], V[j], i-1));
}
}
res += -(km::KM(n));
cout << res << endl;
return 0;
}
K.King of Range
题目:找区间极差大于k的数量。
思路:两个队列,一个最大,一个最小,不断排出靠前的维护极差,如果相减>k说明当前区间是可以的,往后延伸也是可以的。然后排掉靠前的,继续做相同的操作。可能有人会想,如果我找到极差后,在中间又找到了一个满足的,会不会把中间的区间遗漏了。答案是不会。这就是我们这个代码的精妙之处了。我们让left从1到n走,当维护的队列满足时就加上后面的。如果l还没有出现,那么就不弹出元素,让l++。其实就是在模拟了找到了一个right, 我们的left不断向右移动的过程。而当队列元素弹出后不考虑前面的left,这时因为已经被考虑过了。
可以拿这组数据看看(6 1) (3 3 1 3 2 5) (2), 自己加些东西看就知道了。
代码:
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
#define ctx cout << "xxxxxxx" << endl
const int N = 1e5 + 10;
int b[N];
int n, m, k;
int main(){
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n >> m;
for(int i = 1; i <= n; i++) cin >> b[i];
while(m--){
cin >> k;
ll ans = 0;
deque<int> mx, mi;
int l = 1;
for(int i = 1; i <= n; i++)
{
while(mx.size() && b[mx.back()] < b[i]) mx.pop_back();
mx.push_back(i);
while(mi.size() && b[mi.back()] > b[i]) mi.pop_back();
mi.push_back(i);
while(mx.size() && mi.size() && b[mx.front()] - b[mi.front()] > k)
{
// cout << "l = " << l << endl;
if(mi.front() == l)
{
// cout << mi.front() << endl;
mi.pop_front();
}
if(mx.front() == l)
{
// cout << mx.front() << endl;
mx.pop_front();
}
l++;
ans += n-i+1;
}
// cout << i << ' ' << ans << endl;
}
cout<<ans<<endl;
}
}