牛客小白月赛119

赛时成绩如下(最近打的比较好的一次):

A. 来!选!队!长 

你选择了 5 个角色组成小队,TA们的战力按照降序排列分别是 a1,a2,a3,a4,a5​。而你的好友也用 5 个角色组了一个小队,TA们的战力按照降序排列分别是 b1,b2,b3,b4,b5​。一个小队的总实力,等于队长战力的两倍,加上其余四个队员的战力之和。
你的好友是个萌新,TA打算从五个角色中随机取出一个当队长。你想知道,你是否存在一种选队长的方案,使得你的小队总实力有可能严格大于你的好友。 

解题思路:

题目中说有可能严格大于, 见下面代码

#include <bits/stdc++.h>
using namespace std;
void solve() {
    vector<int> a(5), b(5);
    int sum_a = 0, sum_b = 0;
    for (int i = 0; i < 5; i++) {
        cin >> a[i];
        sum_a += a[i];
    }
    for (int i = 0; i < 5; i++) {
        cin >> b[i];
        sum_b += b[i];
    }
    int a_max_num = *max_element(a.begin(), a.end());
    int b_min_num = *min_element(b.begin(), b.end());
    if (a_max_num + sum_a > b_min_num + sum_b)
        cout << "YES" << '\n';
    else
        cout << "NO" << '\n';
}
int main() {
    int t = 1;
    while (t--) {
        solve();
    }
}

B. 抽我选的效率曲

多人游戏由五个玩家组成,现在假设游戏内存在 1000 首歌曲,它们的代号分别是 [1,1000] 之间的整数。现在五个玩家分别进行了决定,它们分别选择了代号为 a1,a2,a3,a4,a5​ 的歌曲。特别地,如果 ai=0 代表第 i 个玩家决定放弃选歌。
在每个人作出决定之后,如果至少有一人有选歌,系统会随机从有选歌的玩家中等概率抽取一位,以 TA 挑选的歌曲进行游玩;而如果所有人都放弃选歌,系统就会从 1000 首歌中等概率随机抽取一首歌进行游玩。
现在,给定整数 k,请问系统最终抽出代号为 k 的歌曲的概率是多少?请用最简分数表示。
特殊地,如果概率是 0,那么请输出 0/1;如果概率是 1,那么请输出 1/1。 

解题思路:统计5个人选中代号的为k的歌曲的个数, 如果ai=0, 那么就随机从1000首选, 选中的概率为1/1000

#include <bits/stdc++.h>
using namespace std;
void solve() {
    vector<int> a(5);
    int k;
    for (int i = 0; i < 5; i++) {
        cin >> a[i];
    }
    cin >> k;
    int cnt = 0;
    int cnt_1 = 0;
    for (int x : a) {
        if (x != 0) {
            cnt++;
            if (x == k)
                cnt_1++;
        }
    }
    if (cnt == 0) {
        cout << "1/1000" << '\n'; return;
    }
    if (cnt_1 == 0) {
        cout << "0/1" << '\n';  return;
    }
    if (cnt_1 == cnt) {
        cout << "1/1" << '\n';  return;
    }

    int g = gcd(cnt_1, cnt);
    cout << (cnt_1 / g) << "/" << (cnt / g) << '\n';
}
int main() {
    int t;
    cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}

 C. 睡前床边看LIVE

 一共有 n 个豆腐人,每个豆腐人都染上了一个颜色。它们能看见其它 n−1 个豆腐人的颜色,但是无法知道自己是什么颜色。现在你对每个豆腐人都问了相同的问题:“在你看见的这 n−1 个豆腐人中,最多有多少个豆腐人,它们的颜色是一样的?”每个豆腐人都向你回答了一个整数。
请问,你是否能根据以上的信息,判断出一定有豆腐人说谎了?

补充说明:在本题中,一定有豆腐人说谎了,等价于不存在任何一种染色方案使得每个豆腐人的回答与实际相符。 

解题思路:

先进行排序, 如果数组a中最小值比最大值至少小2, 就输出“Lie”

这是因为所有豆腐人看到的其他颜色的频率最多相差1,取决于自己的颜色是否属于最大频率的颜色

eg: 4 4 4 5 5 5 5/4 4 4 5 5 5 5 5 => 站在4的角度为4/5,站在5的角度为3/4

最多不会相差1

然后统计最大值和次大值, 

设最大频率的颜色为A,频率为m。那么:

1.如果一个豆腐人自己是颜色A,那么它看到的其他颜色A的频率是m-1​​​​

2.如果一个豆腐人自己不是颜色A,那么它看到的其他颜色A的频率是m

情况 1:cnt_1 == 0(所有回答都是 m):

这意味着所有豆腐人看到的 m 个同色豆腐人,说明:

要么所有豆腐人颜色相同(此时 m = n-1,因为自己看不到自己)

要么 m 足够小,使得即使某些豆腐人不是 A,也能看到 m 个 A(即 n >= 2 * m)

情况 2:cnt_1 > 0(存在回答 m - 1):

这些回答必须来自 A 颜色的豆腐人(因为它们看到 m-1 个 A)

所以 A 颜色的豆腐人数量 = cnt_1(因为每个 A 颜色的豆腐人都回答 m-1)

但 A 颜色的实际数量是 m(因为 m 是 m)

所以必须 cnt_1 == m

#include <bits/stdc++.h>
using namespace std;
void solve() {
    int n;
    cin >> n;
    vector<int> a(n);
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    sort(a.begin(), a.end());
    if (a[0] < a[n - 1] - 1) {
        cout << "Lie" << '\n';
        return;
    }
    int cnt = 0, cnt_1 = 0;
    for (int x : a) {
        if (x == a[n - 1])
            cnt++;
        if (x == a[n - 1] - 1)
            cnt_1++;
    }
    bool f = false;
    if (cnt_1 == 0) {
        if (n == a[n - 1] + 1 || n >= 2 * a[n - 1]) {
            f = true;
        }
    } else {
        if (cnt_1 == a[n - 1]) {
            f = true;
        }
    }
    if (f)
        cout << "Other" << '\n';
    else
        cout << "Lie" << '\n';
}

int main() {
    int t;
    cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}

 D.世界树上找米库

 一共有 n个地点,它们由 n−1 条长度为 1 的双向道路连成了一棵无根树结构。其中,如果一个地点只延伸出了一条道路,那么这个地点将称为 Sekai 点。
Miku 点的定义如下:

Miku 点一定不是 Sekai 点。
Miku 点是符合上一个条件的所有地点中,与相距最近的 Sekai 点距离最大的点。    

根据以上信息,请你找出所有的 Miku 点吧!

解题思路: 

构建邻接表并记录每个节点的度数。

将所有 Sekai 点(度数为 1 的叶子节点)作为多源,执行 BFS,计算每个节点到最近 Sekai 点的最短距离 dist[i]。

在所有非 Sekai 点(度数 > 1)中,找到距离最近 Sekai 点距离的最大值 md,所有满足 dist[i] == md 的节点就是 Miku 点。

输出它们的数量和从小到大的编号。

#include <bits/stdc++.h>
using namespace std;
void solve() {
    int n;
    cin >> n;
    vector<vector<int>> g(n + 1);
    vector<int> d(n + 1, 0);
    for (int i = 1; i < n; i++) {
        int u, v;
        cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
        d[u]++;
        d[v]++;
    }
    queue<int> q;
    vector<int> dt(n + 1, -1);
    for (int i = 1; i <= n; i++) {
        if (d[i] == 1) {
            dt[i] = 0;
            q.push(i);
        }
    }
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        for (int v : g[u]) {
            if (dt[v] == -1) {
                dt[v] = dt[u] + 1;
                q.push(v);
            }
        }
    }
    int max_num = 0;
    for (int i = 1; i <= n; i++) {
        if (d[i] > 1) {
            max_num = max(max_num, dt[i]);
        }
    }
    vector<int> ans;
    for (int i = 1; i <= n; i++) {
        if (d[i] > 1 && dt[i] == max_num) {
            ans.push_back(i);
        }
    }
    int ans_n = ans.size();
    cout << ans_n << endl;
    for (int x : ans)
        cout << x << " ";
    cout << '\n';
}
int main() {
    int t;
    cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}

 E, 森友会里打音游

你的背包有 n 个物件,现在你按顺序依次将它们放在了草坪上,从左到右第 i 个物品的大小为 ai​。
现在你可以移除一些物件(也允许不移除),请问你最多可以保留多少个物件,使得剩下的物件中,对于任意相邻的两个物件,二者大小均有整除关系?(如果只剩一个物件默认符合条件)
在此,我们称两个正整数 a,b 有「整除关系」,当且仅当 a∣b 或 b∣a 成立,即 a 是 b 的约数或 a 是 b 的倍数。 

解题思路:

DP 数组定义:

dp[x] 表示以数值 x 结尾的最长符合条件的序列的长度

初始化时,dp 数组的所有元素都为 0,因为还没有处理任何物件

动态规划更新:

遍历每个物件 x:

1:找到 x 的所有约数(包括 x 本身),并计算这些约数对应的 dp 值的最大值 k

对于每个约数 i(i 是 x 的约数),更新 k = max(k, dp[i])

同时,检查 x / i 是否也是约数(避免重复计算),如果是,也更新 k

2:找到 x 的所有倍数(不超过 max_num),并计算这些倍数对应的 dp 值的最大值 k。

对于 x 的倍数 m(m = x, 2x, 3x, ..., <= max_num),更新 k = max(k, dp[m])。

步骤 3:更新 dp[x] 为 k + 1(因为当前物件 x 可以接在最长序列的后面)

步骤 4:更新全局最大值 ans,记录当前的最长序列长度

#include <bits/stdc++.h>
using namespace std;
void solve() {
    int n;
    cin >> n;
    vector<int> a(n);
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    int max_num = *max_element(a.begin(), a.end());
    vector<int> dp(max_num + 1, 0);
    int ans = 1;
    for (int x : a) {
        int k = 0;
        for (int i = 1; i * i <= x; i++) {
            if (x % i == 0) {
                k = max(k, dp[i]);
                int j = x / i;
                if (j != i) {
                    k = max(k, dp[j]);
                }
            }
        }
        for (int m = x; m <= max_num; m += x) {
            k = max(k, dp[m]);
        }
        dp[x] = max(dp[x], k + 1);
        ans = max(ans, k + 1);
    }
    cout << ans << '\n';
}
int main() {
    int t;
    cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}

 F. 这招太狠了烤学妹

你找出了朋友的一个长度为 n 的音游打击序列,为了简化模型,这个序列只有 o(命中)和 x(不命中)。

下面定义音游打击序列每个位置的当前连击数:

有一个累加器,初始值设为 0。
将序列从左到右依次扫描,对于当前某个字符,若它是 o\text{o}o,那么累加器加 111,否则累加器重置为 0。
序列每个位置的当前连击数,等于扫描过这个位置后累加器的值。
例如,对于音游打击序列 oooxoo,每个位置的当前连击数依次是 1,2,3,0,1,2。

整个音游打击序列的分数,等于打击序列每个位置的当前连击数之和。例如,对于音游打击序列 oooxoo,分数等于 1+2+3+0+1+2=9。

现在你有 k 次动手脚的机会,每次机会可以将这个打击序列的一个 o 修改为 x。k 次机会可以全部使用、部分使用或完全不使用。你想知道所有修改方案中,分数的最小值可以是多少。

解题思路: 

对于一段连续的o, 在中间进行x一次是最优的

对于一个极长连续 o 段,我们想通过修改 k 处让分数最小,那么分割出来的若干段长度要尽可能平均。
证明:
假设切出的两个连续 o 段,长度均为 a;此时把修改的地方往相邻处移动一格,切出的两段长度就变为 a−1,a+1。
那么两种情况的分数分别为 a2+a和 a2−a+a2+3a+2/2=a2+a+1,显然前者更小。
相似的情况同样可以计算得知,长度尽量靠近时分数更小,这里就不写了。
所以我们可以定义一个函数 F(i,j) 表示把一个长度为 i 的连续 o 段修改 j 处得到的最小分数。
定义 s(x)=x(x+1)/2​,即一个长度为 x 的连续 o 段的分数。
那么 F(i,j) 相当于有 j 处被抹掉了,要把剩余的 i−j 处切成 j+1 段,且尽可能切得均匀。
设 k=⌊i−jj+1⌋,  r=(i−j)−(j+1)k,  p=(j+1)−r。三个量 k,r,p 分别代表切割出的最小段的长度、切出长度为 k+1 的段数量、切出长度为 k 的段数量。
那么 F(i,j)=p×s(k)+r×s(k+1)。
对于一般的情况,我们会出现多个连续 o 段。

我们可以设计一个贪心的策略,希望每多切一刀,答案就会减少得尽可能多。那我们要怎么切呢?

一个很常见的误区就是每次选一段,切成均等的两半。这是不对的,原因可以看上面的结论。假设你可以切两刀,最优的分割方案是三等分,而不是对半后再在某一段对半。

所以对于初始情况的每个极长连续 o 段,我们假设记录它初始长度 L,当前已经被切的次数 kkk。那么对于这被切了 k 次的段,多给你一次切一刀的机会,带来的减少量是 F(L,k)−F(L,k+1)。这是一个反悔贪心式的操作,将补偿量作为更换方案的指标。

如果让你选择多个原始连续段的其中一个切,你需要挑出减少量最大的一个原始段。切完以后,把当前的减少量更新为 F(L,k+1)−F(L,k+2),然后在这些原始段内重新选减少量最大的段,进行下一次切割。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
ll get(int len, int k) {
	ll ans = 0;
	int block = (len - k) / (k + 1);
	int lft = len - k - (k + 1) * block;
	int cnt = k + 1 - lft;

	ans = 1ll * block * (block + 1) / 2 * cnt + 1ll * (block + 1) * (block + 2) / 2 * lft;

	return ans;
}

void solve() {
	int n, k;
	cin >> n >> k;

	string s;
	cin >> s;
	s = " " + s;
	ll ans = 0;

	priority_queue<array<ll, 3>> q;
	int cnt = 0;
	for (int i = 1; i <= n; i++) {
		if (s[i] == 'o') {
			cnt++;
		}
		else {
			if (cnt) {
				ans += 1ll * cnt * (cnt + 1) / 2;
				q.push({get(cnt, 0) - get(cnt, 1), cnt, 0});
			}

			cnt = 0;
		}
	}

	if (cnt) {
		ans += 1ll * cnt * (cnt + 1) / 2;
		q.push({get(cnt, 0) - get(cnt, 1), cnt, 0});
	}

	while (k-- && q.size()) {
		auto [v, len, k] = q.top();
		q.pop();

		ans -= v;
		k++;
		if (k + 1 <= len) {
			q.push({get(len, k) - get(len, k + 1), len, k});
		}
	}

	cout << ans << endl;
}

int main() {
    int t = 1;
    cin >> t;
    while (t--) solve();
    return 0;
}

 感谢大家的点赞和关注,你们的支持是我创作的动力!

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值