2021暑期牛客5,B,D,H,J,K 补题

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;
    }
    
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值