ABC402题解

rk891,at rating 1345 → \to 1372,菜死了。

G 太过毒瘤所以就这篇文章不写了,到时候单独开一个坑来写 G 题的题解。毕竟全球就 5 5 5 个人做出来的题不是想补就能补的

F 因为觉得挺好,所以也要单开一个文章。

A.CBC

签到题,送分的。

题意

给定一个由大小写英文字母组成的字符串 S S S。请输出按原顺序连接 S S S 中所有大写字母所得的字符串。

思路

遍历整个字符串,找到大写字母就输出即可。

#include <bits/stdc++.h>
using namespace std;
string s;

int main() {
	cin >> s;
	for (auto i : s)
		if (i >= 'A' && i <= 'Z')
			cout << i;
	return 0;
}

B.Restaurant Queue

仍然是签到提。

题意

高桥希望管理 AtCoder 餐厅前的等候队伍。起初,等候队伍是空的。每个排队的人都持有一张餐票,上面写着他们要点的菜的菜单编号。

按顺序处理 Q Q Q 个查询。查询有两种类型,格式如下:

  • 1 X:一个人拿着菜单编号为 X X X 的票加入等候队伍的末尾。
  • 2:高桥引导等候队伍最前面的人进入餐厅。在该人的票上打印菜单编号。

思路

从末尾进入,从头部弹出,这显然是一个队列的操作方式。

使用 STL 中的队列模拟即可。

#include <bits/stdc++.h>
using namespace std;
queue<int> q;

int main() {
	int x;
	cin >> x;
	while (x--) {
		int op;
		cin >> op;
		if (op == 1) {
			int a;
			cin >> a;
			q.push(a);
		} else {
			cout << q.front() << endl;
			q.pop();
		}
	}
	return 0;
}

C.Dislike Foods

题面

AtCoder 餐厅使用 N N N 种配料,编号从 1 1 1 N N N

餐厅提供 M M M 道菜,编号从 1 1 1 M M M 。菜肴 i i i 使用了 K i K_i Ki 种食材,即 A i , 1 , A i , 2 , … , A i , K i A_{i,1}, A_{i,2}, \ldots, A_{i,K_i} Ai,1,Ai,2,,Ai,Ki

Snuke:目前不喜欢所有的 N N N 食材。他不能吃使用了一种或多种他不喜欢的食材的菜肴,但他可以吃不使用任何不喜欢的食材的菜肴。

在接下来的 N N N 天里,他将每天克服一种不喜欢的食材。在第 i i i 天,他克服了第 B i B_i Bi 种配料,从此他就不再不喜欢这种配料了。

在每个 i = 1 , 2 , … , N i=1,2,\ldots,N i=1,2,,N 中,找出:

  • 在第 i i i 天克服了食材 B i B_i Bi 后,他可以立即在 AtCoder 餐厅吃到的菜的数量。

思路

先讲讲自己的错误:一开始没有看到 B i B_i Bi 导致样例都没过,浪费 2min。

显然可以记录每一个菜品的配料中,喜欢的配料有多少(注意不是)。

于是每一次读入 B i B_i Bi 都可以遍历以它为配料的所有菜,当一个菜品中喜欢的配料等于使用的配料数的时候这个菜品就可以吃了。

#include <bits/stdc++.h>
using namespace std;
int n, m;
const int N = 300010;
vector<int> pos[N];
int len[N], nw[N];

int main() {
	cin >> n >> m;
	for (int i = 1; i <= m; i++) {
		int x;
		cin >> x;
		len[i] = x;
		while (x--) {
			int a;
			cin >> a;
			pos[a].push_back(i);//记录属于的菜,这里不需要去重
		}
	}
	int ans = 0;
	while (n--) {
		int x;
		cin >> x;
		for (auto i : pos[x]) {
			nw[i]++;
			if (len[i] == nw[i])//多了一个可以吃的菜
				ans++;
		}
		cout << ans << endl;
	}
	return 0;
}

D.Line Crossing

题面

在一个顺时针标为 1 , 2 , … , N 1,2,\ldots,N 1,2,,N 的圆上有 N N N 个等距点。

M M M 条不同的线,其中 i i i 条线经过两个不同的点 A i A_i Ai B i B_i Bi ( 1 ≤ i ≤ M ) (1 \leq i \leq M) (1iM) .

求满足 ( i , j ) (i,j) (i,j) 的线对的个数:

  • 1 ≤ i < j ≤ M 1 \le i < j \le M 1i<jM ,且
  • i i i 线与 j j j 线相交。

思路

很容易知道,两条直线相交,当且仅当两条直线不平行。废话

考虑正难则反。

M M M 条直线,一共就可以组成 C M 2 C_{M}^2 CM2 个直线对。可以在这些直线对中将平行的直线对去掉,即可得到答案。

观察样例 1 1 1,发现两条直线平行当且仅当这两条线的两个端点加起来的和相同。

然后写出来代码,发现样例二过不去!

发现样例二的每两个点之间都有一条边,而又发现 ( 4 , 5 ) (4,5) (4,5) ( 1 , 3 ) (1,3) (1,3) 理应平行,但是被我们的结论否掉了。

所以考虑修改结论:两条直线平行当且仅当这两条线的两个端点加起来的和 N N N 同余

所以采用一个桶来计数即可。复杂度 O ( N ) O(N) O(N)

注意要开 longlong。

#include <bits/stdc++.h>
#define int long long
using namespace std;
int n, m;
const int N = 2000010;
int cnt[N];

signed main() {
	cin >> n >> m;
	for (int i = 1; i <= m; i++) {
		int x, y;
		cin >> x >> y;
		cnt[x + y]++;
	}
	int ans = m * (m - 1) / 2;
	for (int i = 1; i <= n; i++)
		ans -= (cnt[i] + cnt[i + n]) * (cnt[i] + cnt[i + n] - 1) / 2;
	cout << ans << endl;
	return 0;
}

E.Payment Required

题面

30XX 年,每次向某个竞赛提交作品都需要付款。

竞赛有 N N N 个问题。问题 i i i S i S_i Si 分,每次提交需花费 C i C_i Ci 日元。

当高桥提交 i i i 问题时,他解决问题的概率为 P i P_i Pi %。每次提交的结果都与其他结果无关。

他有 X X X 日元。他不能提交任何会导致他的总支出超过 X X X 日元的方案。

求当他选择最大值的提交时,他所解决问题的总分的期望值。

他可以在看到上一次提交的结果后再决定下一次提交哪个问题,多次解决同一个问题所获得的分数与一次解决问题所获得的分数相同。

思路

一开始我以为是高斯消元,原来是期望 dp。

期望 dp 是我的弱点,Meet-in-the-middle 也是。这次比赛一下子戳中了我两个痛点,令人忍俊不禁。


首先容易发现本题中存在一个背包 DP 的模型:

i i i 题的分值为 S i S_{i} Si,每次提交解答需要花费 C i C_{i} Ci 日元,总共只有 X X X 日元,问最大期望得分。

相当于:

i i i 个物品的价值为 S i S_{i} Si,重量为 C i C_{i} Ci,背包总容量为 X X X,问背包能装下的最大期望价值。

但是本题并不属于那些经典的背包 DP 模型,因为我们无法确定某个物品会被选择几次才会加入背包。

但是,我们可以确定每次选择时,哪些物品可能会被我们选择,哪些物品不会被我们选择。即我们已经通过的题目就不需要再次选择了,于是考虑能否保存每个题目的是否已经通过。注意到 N N N 很小只有 8 8 8,所以我们将每个题目是否已经通过状压进 DP 维度即可:

  • 状态设计: f i , j f_{i,j} fi,j 代表花了 i i i 日元,通过的题目的集合为 j j j 时的最大期望得分;
  • 初始状态: f 0 , ∅ = 0 f_{0,\emptyset}=0 f0,=0
  • 状态转移: 对于某未通过题目 k k k,我们尝试提交这道题目:

故:

f i , j = max ⁡ ( P k ⋅ f i − C k , j − k + S k , ( 1 − P k ) ⋅ f i − C k , j ) f_{i,j}=\max(P_{k}\cdot f_{i - C_{k},j - k}+S_{k},(1 - P_{k})\cdot f_{i - C_{k},j}) fi,j=max(PkfiCk,jk+Sk,(1Pk)fiCk,j)

  • 通过:概率为 P k P_{k} Pk,结果花费了 C k C_{k} Ck 日元,题目通过状态发生改变(题目 k k k 通过了),获得了 S k S_{k} Sk 分;
  • 未通过:概率为 1 − P k 1 - P_{k} 1Pk,结果花费了 C k C_{k} Ck 日元,题目通过状态没有发生改变。
  • 所求结果: f X , [ 1 , N ] f_{X,[1,N]} fX,[1,N]
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 10;
int s[N], c[N], p[N];
double dp[5010][(1 << N + 1) + 10];

signed main() {
	int n, m;
	cin >> n >> m;
	for (int i = 0; i < n; i++)
		cin >> s[i] >> c[i] >> p[i];
	int mask = 1 << n;
	for (int i = 1; i <= m; i++)
		for (int j = 0; j < mask; j++)
			for (int k = 0; k < n; k++) {
				if (((1 << k) & j) == 0 || c[k] > i)
					continue;
				double pk = p[k] / 100.0;
				dp[i][j] = max(dp[i][j], pk * (dp[i - c[k]][j - (1 << k)] + s[k]) + (1 - pk) * dp[i - c[k]][j]);
			}
	printf("%.8lf", dp[m][mask - 1]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值