AtCoder Beginner Contest 221 复盘

A. Seismic magnitude scales

一遍 AC。

int a, b;

int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin >> a >> b;
    if (a > b) std::swap(a, b);
    cout << (1ll << (5 * (b - a))) << endl;
    return 0;
}

B. typo

一遍 AC。

std::string a, b;

int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin >> a >> b;
    if (a == b) {
        cout << "Yes" << endl;
        return 0;
    }
    for (int i = 0, siz = (int) a.size(); i < siz - 1; ++i) {
        std::swap(a[i], a[i + 1]);
        if (a == b) {
            cout << "Yes" << endl;
            return 0;
        }
        std::swap(a[i], a[i + 1]);
    }
    cout << "No" << endl;
    return 0;
}

C. Select Mul

读完题稍微楞了一下然后才开始写的。写得有些麻烦,还出了点 typo。

#错误警示:操作数位的时候,一定要记清乘 base 的多少次方!尤其是 Hash 的时候!

int digs[10 + 5], cnt;
int ord[10 + 5];
lli pow10[100];

void extract(int x) {
    while (x) {
        digs[++cnt] = x % 10;
        x /= 10;
    }
}

int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int x; cin >> x; extract(x);
    pow10[0] = 1;
    for (int i = 1; i <= cnt; ++i) {
        ord[i] = i;
        pow10[i] = pow10[i - 1] * 10ll;
    }
    lli ans = 0;
    do {
        lli num1 = 0, num2 = 0;
        for (int i = 1; i <= cnt; ++i) num2 = num2 * 10 + digs[ord[i]];
        // DEBUG(num2);
        for (int cut = 1; cut < cnt; ++cut) {
            // cut between x, x + 1
            num1 = num1 * 10 + digs[ord[cut]];
            num2 -= digs[ord[cut]] * pow10[cnt - cut]; // remember to -1
            // DEBUG(digs[ord[cut]]);
            // DEBUG(num1); DEBUG(num2);
            if ((cut != 1 && digs[ord[1]] == '0') || (cut != cnt - 1 && digs[ord[cut + 1]] == '0')) continue;
            ans = std::max(ans, num1 * num2);
        }
    } while (std::next_permutation(ord + 1, ord + 1 + cnt));
    cout << ans << endl;
    return 0;
}

D. Online games

第一次过不去样例三,又读了一遍题才发现不能直接差分,还需要加上原值的差。

一遍 AC,虽然有点运气成分。

const int MAXN = 2e5 + 10;
const int MAXNUM = 4e5 + 10;

int n, aa[MAXN], end[MAXN];
int nums[MAXNUM], cnt;
int diff[MAXNUM];
int ans[MAXN];

int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin >> n;
    rep (i, 1, n) {
        int len;
        cin >> aa[i] >> len; end[i] = aa[i] + len;
        nums[++cnt] = aa[i];
        nums[++cnt] = aa[i] + len;
    }
    std::sort(nums + 1, nums + 1 + cnt);
    cnt = (std::unique(nums + 1, nums + 1 + cnt) - nums - 1);
    int maxend = 0;
    rep (i, 1, n) {
        aa[i] = std::lower_bound(nums + 1, nums + 1 + cnt, aa[i]) - nums;
        end[i] = std::lower_bound(nums + 1, nums + 1 + cnt, end[i]) - nums;
        ++diff[aa[i]]; --diff[end[i]];
        maxend = std::max(maxend, end[i]);
    }
    rep (i, 1, maxend) {
        diff[i] += diff[i - 1];
        ans[diff[i]] += nums[i + 1] - nums[i];
    }
    rep (i, 1, n) {
        cout << ans[i] << ' ';
    } cout << endl;
    return 0;
}

E. LEQ

首先可以把题意转成求

[sum_{ ext{for every }i<j ext{ s.t. }a_ileq a_j}2^{j - i - 1} ]

似乎可以用类似树状数组求逆序对一样的思想搞一搞?但是到这我就不会了。

赛后才知道,需要拆掉 (2^{j - i - 1}) 这个式子,得到 (frac{2^j}{2^{i + 1}}) 这个东西,然后我们从后往前固定 (i) ,式子就变成了求

[sum_ifrac{1}{2^{i + 1}}sum_{ ext{for every }j > i ext{ s.t. }a_j geq a_i}2^j ]

后面这个东西就很好用权值树状数组维护了:从后往前扫到了 (j),在 (a_j) 这个位置存上一个 (2^j),统计所有 (a_j + 1)(max a) 的和即可;再把这个乘上一个 (2^{i + 1}) 的逆元加入总答案即可。

Special thanks to @plokim.

const int MAXN = 3e5 + 10;
const int HA = 998244353;

void add(int &x, int y) {
    x = (1ll * x + 1ll * y) % HA;
}
int n, aa[MAXN];

struct BIT {
    int seq[MAXN];
    #define lb(x) ((x) & (-(x)))
    void insert(int pos, int x) {
        for (; pos <= n; pos += lb(pos)) add(seq[pos], x);
    }
    int qry(int pos) {
        int r = 0; for (; pos; pos -= lb(pos)) add(r, seq[pos]); return r;
    }
} bit;
int fastpow(int a, int b, int p) {
    int r = 1; while (b) {if (b & 1) r = 1ll * r * a % p; a = 1ll * a * a % p; b >>= 1;} return r;
}

int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin >> n;
    std::map<int, int> lisan;
    rep (i, 1, n) {cin >> aa[i]; lisan[aa[i]] = 0;}
    int cnt = 0;
    for (auto &x : lisan) {
        x.second = ++cnt;
    }
    rep (i, 1, n) aa[i] = lisan[aa[i]];
    int ans = 0;
    for (int i = n; i >= 1; --i) {
        int tmp = 1ll * bit.qry(cnt) - 1ll * bit.qry(aa[i] - 1); // 因为是 ai <= aj 所以这里求的是 [aa[i], maxa]
        (tmp += HA) %= HA;
        tmp = 1ll * tmp * fastpow(fastpow(2, i + 1, HA), HA - 2, HA) % HA;
        add(ans, tmp);
        bit.insert(aa[i], fastpow(2, i, HA));
    }
    cout << ans << endl;
    return 0;
}

F. Diameter set

首先考虑直径 (d) 为偶数的情况,此时是存在直径中点的,把它挑起来当根,那么此时树的最大深度就是 (frac{d}{2}),所有可以选的点就是深度为 (frac{d}{2}) 的点,而且根的每棵子树里至多能选一个。

设根节点的度为 (k),也就是 (k) 棵子树,每棵子树可选的点是 (D_k) 个,那么答案即为 (prod (D_i + 1) - sum D_i - 1),也就是子树里任选一点(可以不选)的方案数乘起来,减去只选一个点的方案数和所有点都不选的方案数。


直径为奇数的情况类似,只不过我们这次不是找直径中点挑起来当根,而是找直径中心边的两个端点,断掉这条边,形成两棵子树,显然这两棵子树的最大深度是 (frac{d - 1}{2}),能选的点即是最大深度的点,每棵子树恰好选一个。答案即是 (D_1D_2)


所有东西都可以通过跑 DFS 搞定,时间复杂度 (O(n)).

代码里处理偶数的做法和上面说的不大一样,而和奇数类似,是把直径中点的所有边都断了,形成 (k) 棵子树,每棵子树最大深度即是 (frac{d}{2} - 1),找这些点即可。有很多地方可以代码复用,但我懒了,直接复制粘贴改改就过了。

const int MAXN = 2e5 + 10;
const int HA = 998244353;

int n;
int diameter;

struct Edge {
    int v, next; bool disabled;
} edge[MAXN << 1]; int head[MAXN], cnt;

inline void addEdge(int u, int v) {
    edge[cnt++] = {v, head[u]}; head[u] = cnt - 1;
}

int dist[MAXN];

void dfs(int u, int fa, int *dis) {
    for (int e = head[u]; e != -1; e = edge[e].next) {
        int v = edge[e].v;
        if (v == fa || edge[e].disabled) continue;
        dis[v] = dis[u] + 1;
        dfs(v, u, dis);
    }
}
std::vector<int> path;
bool dfsCollect(int u, int fa, int end) {
    if (u == end) {
        path.push_back(u); return true;
    }
    bool flag = false;
    for (int e = head[u]; e != -1; e = edge[e].next) {
        int v = edge[e].v;
        if (v == fa || edge[e].disabled) continue;
        if (dfsCollect(v, u, end)) { flag = true; break; }
    }
    if (flag) path.push_back(u);
    return flag;
}

namespace Task_getDiameter {
    void _main() {
        dfs(1, 0, dist);
        int fx = 1;
        rep (i, 1, n) if (dist[fx] < dist[i]) fx = i;
        dist[fx] = 0; dfs(fx, 0, dist);
        int fy = 1;
        rep (i, 1, n) if (dist[fy] < dist[i]) fy = i;
        dfsCollect(fx, 0, fy);
    }
}
int bel[MAXN], blks;
int cnt_dist[MAXN];
void dfsBlocks(int u, int fa, int blk) {
    bel[u] = blk;
    for (int e = head[u]; e != -1; e = edge[e].next) {
        int v = edge[e].v;
        if (v == fa || edge[e].disabled) continue;
        dfsBlocks(v, u, blk);
    }
}
namespace Task_Odd {
    void _main() {
        int da = path[path.size() / 2], db = path[path.size() / 2 - 1];
        for (int e = head[da]; e != -1; e = edge[e].next) {
            int v = edge[e].v;
            if (v != db) continue;
            edge[e].disabled = edge[e ^ 1].disabled = true;
            break;
        }
        dfsBlocks(da, 0, ++blks); dfsBlocks(db, 0, ++blks);
        dist[da] = dist[db] = 0; dfs(da, 0, dist); dfs(db, 0, dist);
        for (int i = 1; i <= n; ++i) {
            if (dist[i] == diameter / 2) ++cnt_dist[bel[i]];
        }
        cout << 1ll * cnt_dist[1] * cnt_dist[2] % HA << endl;
    }
}
namespace Task_Even {
    void _main() {
        int dx = path[diameter / 2];
        std::vector<int> roots;
        for (int e = head[dx]; e != -1; e = edge[e].next) {
            int v = edge[e].v;
            edge[e].disabled = edge[e ^ 1].disabled = true;
            roots.push_back(v);
        }
        for (auto v : roots) {
            dist[v] = 0; dfs(v, 0, dist);
            dfsBlocks(v, 0, ++blks);
        }
        for (int i = 1; i <= n; ++i) {
            if (dist[i] == diameter / 2 - 1) ++cnt_dist[bel[i]];
        }
        int mul = 1, sum = 0;
        for (int i = 1; i <= blks; ++i) {
            mul = 1ll * mul * (cnt_dist[i] + 1) % HA;
            (sum += cnt_dist[i]) %= HA;
        }
        cout << (mul - sum - 1 + HA) % HA;
    }
}
int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    memset(head, -1, sizeof head);
    cin >> n;
    rep (i, 1, n - 1) {
        int u, v; cin >> u >> v;
        addEdge(u, v); addEdge(v, u);
    }
    Task_getDiameter::_main();
    diameter = (int) path.size() - 1;
    if (diameter & 1) Task_Odd::_main();
    else Task_Even::_main();
    return 0;
}

G. Jumping sequence

首先把坐标系逆时针旋转 45°,那么上下左右跳 (D) 步就变成了坐标变换 ((X pm D, Y pm D))(正负号任取一种,一共 4 种,对应上下左右)。

于是这里就可以把两维分开考虑了:我们需要对于(旋转后的,下同)X 轴找到一个系数序列 (t_i in {-1, 1}) 使得 (sum t_iD_i = N + M)(Y 轴同理,只不过是 (N - M)

从这里我们就能得出 ((X pm D, Y pm D)) 的方向选择了:对于 X 轴,如果 (t_i = -1) 则方向从 L 和 D 中选;对于 Y 轴,如果 (t_i = -1) 则方向从 D 和 R 中选;反之亦然。通过对两个 (t_i) 的确定,我们就能唯一确定当前的方向了,这是我们最后输出方案的关键。

但是这样做还是不好做,直接 DP 复杂度太高还没法优化,于是我们考虑进一步转化:设 (S = sum D_i),于是题目转化成了求系数序列 (c_i) 使得 (sum c_iD_i = frac{S + N + M}{2}) (Y 轴为 (frac{S + N - M}{2})),其中 (c_i in {0, 1})。显然,当 (c_i = 1)(t_i = 1)(c_i = 0)(t_i = -1)。无解情况就是右面的东西不是非负整数。

这个东西就是一个背包求是否有可行解,时间复杂度是 (O(NS)) 的,看似和上面没有变化,但是可以用 bitset 优化,时空都刚好能卡过。

最后输出方案的时候,从后往前找,如果 (c_i) 可以为 (0) 就尽量选择 (0),否则为 (1),然后就知道了对应的 (t_i),进而知道了当前走的是哪个方向。

const int MAXN = 2000 + 10;
const int MAXS = 3600000 + 10;
const char cho[] = { 'L', 'U', 'D', 'R' };

int n, a, b;
int dd[MAXN];
std::bitset<MAXS> dp[MAXN];

int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin >> n >> a >> b;
    int s = 0;
    rep (i, 1, n) {
        cin >> dd[i];
        s += dd[i];
    }
    if ((s + a + b) & 1 || std::abs(a + b) > s || std::abs(a - b) > s) {
        cout << "No" << endl; return 0;
    }
    dp[0][0] = 1;
    rep (i, 1, n) {
        dp[i] = dp[i - 1] | (dp[i - 1] << dd[i]);
    }
    int fx = (s + a + b) >> 1, fy = (s + a - b) >> 1;
    if (!dp[n][fx] || !dp[n][fy]) {
        cout << "No" << endl; return 0;
    }
    cout << "Yes" << endl;
    std::string ans;
    for (int i = n; i >= 1; --i) {
        int k = 0;
        // DEBUG(fx); DEBUG(fy);
        if (!dp[i - 1][fx]) fx -= dd[i], ++k;
        if (!dp[i - 1][fy]) fy -= dd[i], k += 2;
        // DEBUG(dp[i].to_string());
        ans = cho[k] + ans;
    }
    cout << ans << endl;
    return 0;
}
原文地址:https://www.cnblogs.com/handwer/p/15406200.html