AtCoder Beginner Contest 223 复盘
打得很烂,预计 1500 只拿到了 700,甚至比 VP 打得还要差。
A
一遍 AC。
int main() {
int x = read();
if (x % 100 == 0 && x > 0) puts("Yes");
else puts("No");
return 0;
}
B
一遍 AC。
const int MAXN = 2000 + 10;
std::string ss;
std::string strs[MAXN];
int main() {
cin >> ss;
strs[1] = ss;
for (int i = 2; i <= ss.length(); ++i) {
strs[i] = strs[i - 1];
char last = strs[i][0];
strs[i].erase(strs[i].begin());
strs[i] += last;
}
std::sort(strs + 1, strs + 1 + ss.length());
cout << strs[1] << endl;
cout << strs[ss.length()] << endl;
return 0;
}
C. Doukasen
垃圾解方程题,我被卡在第三个样例上了,没调出来,换了题解的写法才过。
其实这种解方程题并不用非得去找到特定位置然后列数据解方程,可以直接去把等式的值求出来,然后用这个值去求解,比如这个题的方程就是 \(\mathrm{ltime} + \frac{x}{B} = \mathrm{rtime} + \frac{A - x}{B}\),等式的意义是两边烧的时间相等(都是 \(\frac{1}{2}\sum\frac{A}{B}\),于是可以直接求这个东西,再带回原式求解。
const int MAXN = 1e5 + 10;
int n;
double aa[MAXN], bb[MAXN];
double burntime[MAXN];
double prefix[MAXN], suffix[MAXN];
int main() {
n = read();
for (int i = 1; i <= n; ++i){
aa[i] = read(); bb[i] = read();
burntime[i] = (double) aa[i] /(double) bb[i];
}
for (int i = 1; i <= n; ++i) {
prefix[i] = prefix[i - 1] + burntime[i];
}
double len = 0;
double tim = prefix[n] / 2.0;
rep (i, 1, n) {
if (prefix[i] > tim) {
double dt = tim - prefix[i - 1];
len += bb[i] * dt;
printf("%.6lf\n", len);
return 0;
}
len += aa[i];
}
return 0;
}
D. Restricted Permutation
第一眼看过去和《菜肴制作》差不多,其实还有些不一样,令最小的最靠前和令字典序最小这两个是不同的,前者需要建反图反着跑,后者建正图就可以了。核心思想都是利用优先队列代替普通队列。
const int MAXN = 2e5 + 10;
int n, m;
std::vector<int> G[MAXN];
int ind[MAXN];
int main() {
n = read(); m = read();
rep (i, 1, m) {
int u = read(); int v = read();
G[u].push_back(v);
++ind[v];
}
std::vector<int> ord;
std::priority_queue<int, std::vector<int>, std::greater<int> > q;
for (int i = 1; i <= n; ++i) {
if (!ind[i]) {
q.push(i);
}
}
while (!q.empty()) {
int u = q.top(); q.pop();
ord.push_back(u);
std::sort(ALL(G[u]));
forall (G[u], i) {
int v = G[u][i];
--ind[v];
if (!ind[v]) {
q.push(v);
}
}
}
if (ord.size() != n) printf("-1");
else for (auto v : ord) printf("%d ", v);
puts("");
return 0;
}
F. Parenthesis Checking
也是套路了,把左括号看成 \(+1\) 右括号看成 \(-1\),一个括号串合法等价于整个串的和为 \(0\) 且中间没有掉到负数(相对值),所以需要对原序列维护一个区间和,前缀和的最小值,后面这个东西有点反直觉但是也能直接维护。
考场上我懒得去费劲想这个东西了,所以我选择用线段树维护前缀和数组,一次操作等价于修改两个后缀 \([l, n]\) 和 \([r, n]\),查询就是查 \(r\) 和 \(l - 1\) 两个点的值和 \(l\) 到 \(r\) 的区间最小值。
然后区间最值写挂了,调一场没调出来。
#错误警示:写线段树的时候想清楚各种值是怎么维护的,不要“我觉得它是对的”。
const int MAXN = 4e5 + 10;
int n, q;
char ss[MAXN];
namespace Segt {
lli minx[MAXN << 2], sum[MAXN << 2];
lli tag[MAXN << 2];
#define ls (p << 1)
#define rs (p << 1 | 1)
void Update(int p) {
sum[p] = sum[ls] + sum[rs];
minx[p] = std::min(minx[ls], minx[rs]);
}
void buildTree(int p, int l, int r, lli *seq) {
if (l == r) { minx[p] = sum[p] = seq[l]; return; }
int mid = (l + r) >> 1;
buildTree(ls, l, mid, seq); buildTree(rs, mid + 1, r, seq);
Update(p);
}
void Add(int p, lli l, lli r, lli k) {
sum[p] += k * (r - l + 1);
minx[p] += k;
tag[p] += k;
}
void Pushdown(int p, int l, int r) {
if (!tag[p]) return;
int mid = (l + r) >> 1;
Add(ls, l, mid, tag[p]); Add(rs, mid + 1, r, tag[p]);
tag[p] = 0;
}
void Modify(int p, int l, int r, int ll, int rr, lli k) {
if (l == ll && rr == r) { Add(p, l, r, k); return; }
int mid = (l + r) >> 1;
Pushdown(p, l, r);
if (rr <= mid) Modify(ls, l, mid, ll, rr, k);
else if (mid + 1 <= ll) Modify(rs, mid + 1, r, ll, rr, k);
else {
Modify(ls, l, mid, ll, mid, k);
Modify(rs, mid + 1, r, mid + 1, rr, k);
}
Update(p);
}
std::pair<lli, lli> Query(int p, int l, int r, int ll, int rr) {
if (rr < 1) return {0, 0};
if (l == ll && rr == r) return {sum[p], minx[p]};
Pushdown(p, l, r);
int mid = (l + r) >> 1;
if (rr <= mid) return Query(ls, l, mid, ll, rr);
else if (mid + 1 <= ll) return Query(rs, mid + 1, r, ll, rr);
else {
std::pair<lli, lli> lt = Query(ls, l, mid, ll, mid), rt = Query(rs, mid + 1, r, mid + 1, rr);
return {lt.first + rt.first, std::min(lt.second, rt.second)};
}
}
}
lli seq[MAXN];
int main() {
scanf("%d %d", &n, &q);
scanf("%s", ss + 1);
rep (i, 1, n) {
if (ss[i] == '(') seq[i] = 1;
else seq[i] = -1;
seq[i] += seq[i - 1];
}
Segt::buildTree(1, 1, n, seq);
while (q --> 0) {
int pos, l, r; scanf("%d %d %d", &pos, &l, &r);
if (pos == 1) {
lli tl = Segt::Query(1, 1, n, l, l).first - Segt::Query(1, 1, n, l - 1, l - 1).first;
lli tr = Segt::Query(1, 1, n, r, r).first - Segt::Query(1, 1, n, r - 1, r - 1).first;
Segt::Modify(1, 1, n, l, n, -tl + tr);
Segt::Modify(1, 1, n, r, n, -tr + tl);
} else {
std::pair<lli, lli> fx = Segt::Query(1, 1, n, r, r), pref = Segt::Query(1, 1, n, l - 1, l - 1), px = Segt::Query(1, 1, n, l, r);
if ((fx.first - pref.first) || (px.second - pref.first)) puts("No");
else puts("Yes");
}
}
return 0;
}
G. Vertex Deletion
F 调不出了于是决定换题,然而发现自己连 matching 是什么都不知道,百度了一下发现自己固定根的 DP 都不会写。
赛后来学了一下,其实和树上独立集的想法差不多,f[u][0/1]
表示以 u 为根的子树是(1)否(0)选择了一条边的最大匹配数大小,然后用类似的思想转移即可。
求出这个之后,直接换根 dp 就可以了。
const int MAXN = 2e5 + 10;
int n;
std::vector<int> G[MAXN];
int dp[MAXN][2];
// dp[u][0] = sum of max(dp[v][0], dp[v][1])
// dp[u][1] = dp[u][0] + max(dp[v][0] - max(dp[v][0], dp[v][1]) + 1)
int mx[MAXN], tmx[MAXN];
int ans;
void dfs1(int u, int fa) {
forall (G[u], i) {
int v = G[u][i];
if (v == fa) continue;
dfs1(v, u);
dp[u][0] += std::max(dp[v][0], dp[v][1]);
} int sum = dp[u][0];
forall (G[u], i) {
int v = G[u][i];
if (v == fa) continue;
int vlv = dp[v][0] - std::max(dp[v][0], dp[v][1]) + 1;
if (vlv > mx[u]) { tmx[u] = mx[u]; mx[u] = vlv; }
else if (vlv > tmx[u]) tmx[u] = vlv;
} dp[u][1] = sum + mx[u];
}
void dfs2(int u, int fa, int outerdp0, int outerdp1) {
int dp0 = dp[u][0] + std::max(outerdp0, outerdp1);
if (dp0 == std::max(dp[1][0], dp[1][1])) ++ans;
forall (G[u], i) {
int v = G[u][i];
if (v == fa) continue;
int new_outerdp0 = dp0 - std::max(dp[v][0], dp[v][1]);
int addval = mx[u];
if (addval == dp[v][0] - std::max(dp[v][0], dp[v][1]) + 1) addval = tmx[u];
if (u != 1) addval = std::max(addval, outerdp0 - std::max(outerdp0, outerdp1) + 1);
int new_outerdp1 = new_outerdp0 + addval;
dfs2(v, u, new_outerdp0, new_outerdp1);
}
}
int main() {
n = read();
rep (i, 1, n - 1) {
int u = read(); int v = read();
G[u].push_back(v); G[v].push_back(u);
}
dfs1(1, 0);
dfs2(1, 0, 0, 0);
printf("%d\n", ans);
return 0;
}