EDU119(A-E) 题解
Educational Codeforces Round 119 (Rated for Div. 2)
A. Equal or Not Equal
题目大意
给你一个字符串\(s\),仅由
E
和N
组成,其中:
- \(s_i = E\),代表\(a_i = a_{i+1}\)
- \(s_i = N\),代表\(a_i \neq a_{i+1}\)
求问对于给定的模式串是否找到一个可能的字符串。
思路
发现,假如只存在一个\(N\)时,一定首和尾的冲突,否则不会。
因此代码很简单,只需要统计N
的个数,判断是否为1
B. Triangles on a Rectangle
题目大意
给定一个左下角为原点\(O(0, 0)\),右上角\(P(w,h)\)的矩形,给定一些点并且保证给定的点不在corner。
给出四行位于矩形边沿的点分别满足:\((tmp, 0), (tmp, h), (0, tmp), (w,tmp)\)。
求出满足如下要求的面积最大三角形,面积的两倍:必须有两个点位于矩形的同一边上。
思路
每个边维护最小,最大两个点,底为: \(mx - mn\)。
对于在 \(x\) 相同的点,其高肯定是\(h\)。
对于在 \(y\) 相同的点, 其高肯定是\(w\)。
代码如下,之前的代码太傻了。
... 宏定义区域
void solve(){
ll w, h;
read(w, h);
ll ans = 0;
vt<ll> len{h, w};
rep (a, 0, 2){
rep (b, 0, 2){
int k; read(k);
int x, mn = inf, mx = -inf;
rep (i, 0, k) read(x), mn = min(mn, x), mx = max(mx, x);
ans = max(ans, (mx - mn) * len[a]);
}
}
std::cout << ans << "\n";
}
C. BA-String
题目大意
给定一个模式串 \(p\),满足:
- 只有
a
和*
两种符号,其中*
可以扩展为最多 \(k\) 个b
(可以为0个)。求出字典序第\(x\)小的文本串。
Constraint: \(1 \le n \le 2000;0\le k\le 2000;1\le x\le10^{18}\)
思路
首先注意ll
的使用和溢出问题。
首先考虑如何构造不同的文本串\(t\),假设 a
是分隔符,那么找出所有由 a
分隔的区间 \(seg\) 。则总数为:
显然,我们需要第\(x\)小,就是从后往前,分配每一个段内的个数,比如:
**a***
, 我们需要找到第 \(x=20\) 小,其中包含两个区间 \(seg = [2, 3] \rightarrow sz = [7, 10]\),
为了方便取mod
,我们不妨让\(x=x-1\)。
从后往前,对于倒数第一个区间 \(|seg_2| = 10\):使用了 \(19 \% 10 = 9\) 个b
,留下了 \(x=19//10=1\)到下一个区间。
从后往前,对于倒数第二个区间\(|seg_1|=7\) :使用了 \(1 \% 7 = 1\) 个 b
, 留下了\(0\)个,说明结束了。
所以最后答案为:babbbbbbbbb
。
注意,从前往后思路也可以,但是考虑到最多有\(k^{n-1}\)的种类,可能会爆\(ll\),在这种种类很多的题目中,记得考虑是否会溢出的问题,考虑一个不可能会溢出的角度去解决这个问题。
void solve(){
string p;
ll n, k, x;
read(n, k, x);
read(p);
string ans = "";
ll m = p.length(), base = 1, st = 0;
for (int i = m - 1; i >= -1; -- i){
if (x == 0) break;
// i == -1 是为了处理以*开头的情况。
if (i == -1 || p[i] == 'a'){
if (st != 0){
ll nxt, lvf;
base = (k * st + 1);
nxt = (x + base - 1) / base;
lvf = x - base * (nxt - 1);
ans += string(lvf - 1, 'b');
x = nxt;
}
st = 0;
if (i != -1) ans += "a";
}else ++ st;
}
reverse(all(ans));
std::cout << ans << "\n";
}
D. Exact Change
题目大意
有 \(t|t\in[1, 1000]\) 个
case
,有 \(n|n\in [1, 100]\) 种炸薯条,每个薯条的价格是 \(a_i|a_i \in [1,10^9]\),你只有三种硬币,面额分别是:\(1, 2, 3\)。求问,你至少带多少枚硬币可以保证能买任何一种炸薯条。
思路
首先,对于这种能否恰好换钱的问题,容易想到背包和贪心。
显然,我们优先得用\(3\),但是细节之处\(1, 2\) 也会有用。
比如: \(6, 2, 1\), 我们最少拿 \(3\) 而不是 \(4\) 枚硬币:我们可以拿 \(\{3, 2, 1\}\) 便可以购买每个炸薯条。
但是显然,\(1, 2\) 的个数不会很多,并且最终的结果一定在 \(\dfrac{mx}{3}\) 左右浮动。
由于数据范围很小,我们可以考虑暴力枚举 \(1, 2\) 的个数(都小于 \(2\)),同时暴力枚举 \(2\) 的使用个个数,在考虑 \(1\) 的个数使得最终 \(x \% == 0\) 。并记录最少需要的硬币总数。
或者,直接枚举总数,枚举\(1,2\)的数量然后用背包求出一个大小为6背包的使用情况,再枚举\(3\)的使用情况(只需要\([mx-2, mx]\),因为背包最多 \(6\) 恰好等于两个 \(3\)) 我就是这个方法,但是不够简洁。
void solve(){
int n; read(n);
vt<int> f(n); rep (i, 0, n) read(f[i]);
// 最少用量
int mincoins = inf;
for (int one = 0; one <= 2; ++ one){
for (int two = 0; two <= 2; ++ two){
// 要使得每个都能被凑齐的 3 的个数
int maxthree = 0;
for (int i = 0; i < n; ++ i){
// 不同 1, 2 使用个数对应的最少 3 的个数
int minthree = inf;
for (int use2 = 0; use2 <= two; ++ use2){
int x = f[i], lvf;
if (x - 2 * use2 < 0) continue;
x -= 2 * use2;
lvf = x % 3;
if (lvf > one) continue;
minthree = min(minthree, x / 3);
}
maxthree = max(maxthree, minthree);
}
mincoins = min(mincoins, one + two + maxthree);
}
}
wpr(mincoins);
}
关于背包的解法放过链接:🔗
E. Replace the Numbers
题目大意
初始给定一个空的数组\(arr\),给定\(q|q\in[1, 5\cdot 10^5]\)个操作:
1 x
,代表往\(arr\)中添加一个元素 \(x|x\in[1, 5\cdot 10^5]\)。2 x y
,代表\(arr\)中的所有 \(x\) 全都变为 \(y\)。问最后的数组长什么样。
看起来有点像 DSU
,但是考虑:
1 1
1 1
1 2
2 1 3
1 1
假如,直接 DSU
就会变成:\([3, 3, 2, 3]\)
因此,我们需要考虑如何处理使得我们能考虑到 op2
的实效性。
首先,可以考虑暴力(启发式合并),目前我不清楚如何完全搞清楚这个思路的时间复杂度,但是其在多次合并数据结构时比较好用,也就是我们每次把小的合并到大的中,这样可以保证多次合并过程中每个元素最多被移动\(\log n\)次(\(n\) 代表总元素)。
考虑正常的做法,由于增加操作是单调的,因此我们也可以倒过来做。
例如:
1 3
2 3 4
1 3
2 3 5
1 3
倒过来做,先考虑 1 3
则结果数组为:ans = [3]
考虑 2 3 5
,此前的3
将会变为5
目前代表的东西,类似并查集:pa[3] = pa[5]
考虑 1 3
,添加pa[3] = pa[5] = 5
,结果数组为:ans = [3, 5]
考虑 2 3 4
,此前的3
将会变成4
目前代表的东西,pa[3] = pa[4] = 4
考虑1 3
,添加pa[3] = pa[4] = 4
,结果数组为:ans = [3, 5, 4]
倒过来就好了。
const int N = 5e5 + 50;
void solve(){
int q; std::cin >> q;
vt<int> t(q), x(q), y(q);
rep (i, 0, q){
std::cin >> t[i];
if (t[i] == 1) std::cin >> x[i];
else std::cin >> x[i] >> y[i];
}
vt<int> p(N);
iota(all(p), 0);
vt<int> ans;
per (i, q - 1, 0){
if (t[i] == 1) ans.pb(p[x[i]]);
else {
p[x[i]] = p[y[i]];
}
}
reverse(all(ans));
for (int i = 0, _n = ans.size(); i < _n; ++ i)
std::cout << ans[i] << " \n"[i == _n - 1];
}
还有一种,正向直接做,巧妙的用了时间Tag
和并查集去实现,链接🔗