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;
}
原文地址:https://www.cnblogs.com/handwer/p/15419059.html