AtCoder Beginner Contest 264 题解

AtCoder Beginner Contest 264 Solution

更好的阅读体验戳此进入

题面链接

题面 Luogu 链接

abcd 不说了

[ABC264E] Blackout 2

题面

ZK 国有 \(N\) 座城市和 \(M\) 座发电站,我们称城市和发电站为地点。

这些地点的标号为 \(1,2,\dots,N+M\),其中标号 \(1,2,\dots,N\) 是城市,标号 \(N+1,N+2,\dots,N+M\) 是发电站。

这个国家有 \(E\) 条能源传输线路。第 \(i\) 条线路双向连接地点 \(U_i\) 和地点 \(V_i\)。一个城市如果可以通过某些线路到达发电站,则称这个城市是有供电的。

现在有 \(Q\) 条询问。第 \(i\;(1\le i\le Q)\) 条询问,代表第 \(X_i\) 条线路停止工作,并且将来也无法修复。

每次询问后输出有供电的城市。

Solution

没什么可说的,比较显然,把询问离线然后倒着做,用并查集维护加边即可,经典套路。

Code

#define _USE_MATH_DEFINES
#include <bits/stdc++.h>

#define PI M_PI
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}

using namespace std;

mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}

typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;

template < typename T = int >
inline T read(void);

struct Edge{int s, t;}edg[510000];
int N, M, E;
int Q;
ll rans[510000];
bool del[510000];
int qs[510000];
ll ans(0);

class UnionFind{
private:
    int fa[510000];
    int siz[510000];
    bool light[510000];
public:
    UnionFind(void){for(int i = 1; i <= 501000; ++i)fa[i] = i;}
    void Init(void){
        for(int i = 1; i <= N; ++i)siz[i] = 1;
        for(int i = N + 1; i <= N + M; ++i)light[i] = true;
    }
    int Find(int x){return x == fa[x] ? x : fa[x] = Find(fa[x]);}
    void Merge(int s, int t){
        int rs = Find(s), rt = Find(t);
        if(rs == rt)return;
        // printf("Mergeing rs = %d, rt = %d, light is %d %d\n", rs, rt, light[rs] ? 1 : 0, light[rt] ? 1 : 0);
        if(light[rs] ^ light[rt]){
            if(!light[rs] && light[rt])swap(rs, rt);
            ans += siz[rt];
            siz[rs] += siz[rt];
            fa[rt] = rs;
        }else{
            siz[rs] += siz[rt];
            fa[rt] = rs;
        }
    }
}uf;

int main(){
    // freopen("test_18.txt", "r", stdin);
    // freopen("out.txt", "w", stdout);
    N = read(), M = read(), E = read();
    uf.Init();
    for(int i = 1; i <= E; ++i){
        int s = read(), t = read();
        edg[i] = Edge{s, t};
    }Q = read();
    for(int i = 1; i <= Q; ++i)del[qs[i] = read()] = true;
    for(int i = 1; i <= E; ++i)if(!del[i])uf.Merge(edg[i].s, edg[i].t);//, printf("merge : %d %d\n", edg[i].s, edg[i].t);
    for(int i = Q; i >= 1; --i){
        rans[i] = ans;
        uf.Merge(edg[qs[i]].s, edg[qs[i]].t);
    }for(int i = 1; i <= Q; ++i)printf("%lld\n", rans[i]);
    fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
    return 0;
}

template < typename T >
inline T read(void){
    T ret(0);
    int flag(1);
    char c = getchar();
    while(c != '-' && !isdigit(c))c = getchar();
    if(c == '-')flag = -1, c = getchar();
    while(isdigit(c)){
        ret *= 10;
        ret += int(c - '0');
        c = getchar();
    }
    ret *= flag;
    return ret;
}

[ABC264F] Monochromatic Path

题面

给定一个 $ H $ 行 $ W $ 列的 $ 01 $ 矩阵。你可以花费 $ R_i $ 将矩阵第 $ i $ 行进行 $ 01 $ 反转,可以花费 $ C_j $ 将矩阵第 $ j $ 列进行 $ 01 $ 反转。你需要最小化花费,并使得从 $ (1, 1) $ 出发,只能向右或下走到达 $ (H, W) $ 至少有一条路径满足均为 $ 0 $ 或 $ 1 $。

Solution

首先不难想到每一行或一列最多进行一次反转操作,否则是无用的。发现只能向右或向下,则无后效性,故可以尝试 DP。

设 $ dp(i, j, 0/1, 0/1) $ 表示处理到第 $ i $ 行 $ j $ 列,第 $ i $ 行和第 $ j $ 列是否反转的最小花费。

我们设当前状态为 $ dp(i, j, x, y) $,令 $ G_{i, j} $ 表示矩阵的元素,则对于向下走的下一步的转移比较显然:

\[\begin{aligned} dp(i, j, x, y) \rightarrow dp(i + 1, j, 0, y) &\quad G_{i, j} \oplus x \oplus y = G_{i + 1, j} \oplus y \\ dp(i, j, x, y) + R_{i + 1} \rightarrow dp(i + 1, j, 1, y) &\quad G_{i, j} \oplus x \oplus y \neq G_{i + 1, j} \oplus y \end{aligned} \]

对于向右走的下一步也同理可得。

初始值即为 $ dp(1, 1, 0, 0) \leftarrow 0, dp(1, 1, 1, 0) \leftarrow R_1, dp(1, 1, 0, 1) \leftarrow C_1, dp(1, 1, 1, 1) \leftarrow R_1 + C_1 $。

最终答案即为 $ \min{dp(H, W, x, y)} $。

Code

#define _USE_MATH_DEFINES
#include <bits/stdc++.h>

#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}

using namespace std;

mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}

typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;

template < typename T = int >
inline T read(void);

int H, W;
ll R[2100], C[2100];
ll dp[2100][2100][2][2];
bitset < 2100 > G[2100];

int main(){
    H = read(), W = read();
    for(int i = 1; i <= H; ++i)R[i] = read();
    for(int i = 1; i <= W; ++i)C[i] = read();
    for(int i = 1; i <= H; ++i)
        for(int j = 1; j <= W; ++j){
            char c = getchar(); while(!isdigit(c))c = getchar();
            G[i][j] = c == '1' ? true : false;
        }
    memset(dp, 0x3f, sizeof dp);
    dp[1][1][0][0] = 0, dp[1][1][1][0] = R[1], dp[1][1][0][1] = C[1], dp[1][1][1][1] = R[1] + C[1];
    for(int i = 1; i <= H; ++i)
        for(int j = 1; j <= W; ++j)
            for(int x = 0; x <= 1; ++x)
                for(int y = 0; y <= 1; ++y){
                    if((G[i][j] ^ x ^ y )== (G[i + 1][j] ^ y))dp[i + 1][j][0][y] = min(dp[i + 1][j][0][y], dp[i][j][x][y]);
                    else dp[i + 1][j][1][y] = min(dp[i + 1][j][1][y], dp[i][j][x][y] + R[i + 1]);
                    if((G[i][j] ^ x ^ y) == (G[i][j + 1] ^ x))dp[i][j + 1][x][0] = min(dp[i][j + 1][x][0], dp[i][j][x][y]);
                    else dp[i][j + 1][x][1] = min(dp[i][j + 1][x][1], dp[i][j][x][y] + C[j + 1]);
                }
    ll ans(LONG_LONG_MAX);
    for(int x = 0; x <= 1; ++x)for(int y = 0; y <= 1; ++y)ans = min(ans, dp[H][W][x][y]);
    printf("%lld\n", ans);
    fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
    return 0;
}

template < typename T >
inline T read(void){
    T ret(0);
    int flag(1);
    char c = getchar();
    while(c != '-' && !isdigit(c))c = getchar();
    if(c == '-')flag = -1, c = getchar();
    while(isdigit(c)){
        ret *= 10;
        ret += int(c - '0');
        c = getchar();
    }
    ret *= flag;
    return ret;
}

[ABC264G] String Fair

题面

给定 $ n $ 条评分规则,每条存在字符串 $ T_i $ 和得分 $ P_i $。你需要构造一个任意长度但非空的字符串 $ S $,在 $ S $ 中每次出现 $ T_i $ 就会获得 $ P_i $ 得分。你需要最大化得分,输出最大值。若无限大则输出 Infinity

Solution

一个十分精妙的图论。

关键的信息在 $ \lvert T_i \rvert \le 3 $,这样的话我们就可以考虑进行建边。我们令 $ P_{str} $ 表示字符串为 $ str $ 的时候的得分,如果没有该字符串的评分规则即为 $ 0 $。

于是考虑如对于从 $ ab $ 通过 $ c $ 可以有一条边到 $ bc $,边权即为 $ P_{abc} + P_{bc} + P_{c} $。同时注意一些细节,如从空字符串可以通过任意字符,如通过 $ a $ 连结到 $ a $,边权即为 $ P_a $,以及对于长度为 $ 1 $ 的字符串连结到长度为 $ 2 $ 的同理。

这样将图建完之后不难发现只需要 SPFA 跑一遍单源最长路,取最大的 $ dis $ 即可,如果存在正环那么显然无限大。

于是点数为 $ n = 26^2 + 26 + 1 $,边数即为 $ m = 26n $,于是 SPFA 最坏复杂度即为 $ O(nm) $,也就是 $ 26^5 $ 的级别,可以通过。

当然这里我们用 map 实现,手动实现一个 hash() 之后用 unorderer_map 即可去掉 map 的 $ \log $。

Code

#define _USE_MATH_DEFINES
#include <bits/stdc++.h>

#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}

using namespace std;

mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}

typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;



template < typename T = int >
inline T read(void);

struct Edge{
    Edge* nxt;
    int to;
    ll val;
    OPNEW;
}ed[21000];
ROPNEW(ed);
Edge* head[1100];

int N;
int cnt(0);
unordered_map < string, int > score;
map < pair < int, int >, int > mp;
unordered_map < int, pair < int, int > > rmp;
bitset < 1100 > inq;
int dep[1100];
ll dis[1100];
ll ans(LONG_LONG_MIN);

void SPFA(void){
    memset(dis, 0xc0, sizeof dis);
    queue < int > cur;
    cur.push(1); inq[1] = true; dep[1] = 1, dis[1] = 0;
    while(!cur.empty()){
        int p = cur.front(); cur.pop();
        inq[p] = false;
        for(auto i = head[p]; i; i = i->nxt){
            if(dis[p] + i->val > dis[SON]){
                dis[SON] = dis[p] + i->val;
                ans = max(ans, dis[SON]);
                dep[SON] = dep[p] + 1;
                if(dep[SON] > 26 * 26 + 26 + 1)printf("Infinity\n"), exit(0);
                if(!inq[SON])cur.push(SON), inq[SON] = true;
            }
        }
    }
}

int main(){
    N = read();
    for(int i = 1; i <= N; ++i){
        string S; cin >> S;
        score.insert({S, read()});
    }mp.insert({{0, 0}, ++cnt}), rmp.insert({cnt, {0, 0}});
    for(int i = 'a'; i <= 'z'; ++i)mp.insert({{0, i}, ++cnt}), rmp.insert({cnt, {0, i}});
    for(int i = 'a'; i <= 'z'; ++i)for(int j = 'a'; j <= 'z'; ++j)
        mp.insert({{i, j}, ++cnt}), rmp.insert({cnt, {i, j}});
    for(int i = 1; i <= cnt; ++i)for(int j = 'a'; j <= 'z'; ++j){
        ll tot(0); string S;
        if(rmp[i].first)S += (char)rmp[i].first;
        if(rmp[i].second)S += (char)rmp[i].second;
        S += j; tot += score[S];
        while(!S.empty()){
            S.erase(S.begin());
            tot += score[S];
        }
        head[i] = new Edge{head[i], mp[{rmp[i].second, j}], tot};
    }SPFA();
    printf("%lld\n", ans);
    fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
    return 0;
}

template < typename T >
inline T read(void){
    T ret(0);
    int flag(1);
    char c = getchar();
    while(c != '-' && !isdigit(c))c = getchar();
    if(c == '-')flag = -1, c = getchar();
    while(isdigit(c)){
        ret *= 10;
        ret += int(c - '0');
        c = getchar();
    }
    ret *= flag;
    return ret;
}

[ABC264Ex] Perfect Binary Tree

题面

存在一棵以 $ 1 $ 为根节点的 $ n $ 个节点的树,给定序列 $ P_{n - 1} $ 表示 $ [2, n] $ 节点的父亲。给定 $ k $,你需要从 $ [1, k] $ 中选择一些点,对于每一个 $ k $ 一次询问,求有多少种选法使得选出的点为一棵以 $ 1 $ 为根节点的满二叉树。输出 $ k \in [1, n] $ 的答案,答案对 $ 998244353 $ 取模。

Solution

首先一个比较重要的性质就是 $ n $ 个节点的满二叉树层高为 $ \log_2^n $,所以在原树里层高超过 $ \log_2^n $ 的节点就可以直接忽略了。

不难想到 DP,令 $ dp(p, i) $ 表示以 $ p $ 为根节点层高为 $ i $ 的满二叉树方案数。

朴素的转移十分显然,即在其儿子里找到两个层高为 $ i - 1 $ 的满二叉树拼起来即可,即转移为:

\[dp(p, i) = \sum_{u \lt v, u, v \in son_p} dp(u, i - 1) \times dp(v, i - 1) \]

考虑到我们每次询问都是增加一个节点,如此的转移方式复杂度显然是不正确的,考虑优化。不难想到这个式子可以转化为从和平方里去除平方和,即:

\[dp(p, i) = \dfrac{1}{2}((\sum_{u \in son_p}dp(u, i - 1))^2 - \sum_{u \in son_p}dp(u, i - 1)^2) \]

于是这个东西就可以支持 $ O(1) $ 修改了,维护一下和以及平方和即可。注意需要记录上一次的值和新的值,减去旧的加上新的。同时考虑发现每次增加一个点,将会改变该点到 $ 1 $ 节点的整条链,并且只会改变这些点,同时若变化的节点超过 $ \log_2^n $ 层了那么可以直接忽略。

最终的答案即为 $ \sum_{i = 1}^{\lfloor \log_2^n \rfloor}dp(1, i) $。

所以每次修改都是 $ O(\log n) $ 的,最终答案的查询也是 $ O(\log n) $ 的,最终复杂度 $ O(n \log n) $。

Code

#define _USE_MATH_DEFINES
#include <bits/stdc++.h>

#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}

using namespace std;

mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}

typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;

#define MOD (998244353ll)

template < typename T = int >
inline T read(void);

struct Edge{
    Edge* nxt;
    int to;
    OPNEW;
}ed[610000];
ROPNEW(ed);
Edge* head[310000];

ll qpow(ll a, ll b){
    ll ret(1), mul(a);
    while(b){
        if(b & 1)ret = ret * mul % MOD;
        b >>= 1;
        mul = mul * mul % MOD;
    }return ret;
}

int N;
int mxdep;
int inv2;
int dep[310000];
int fa[310000];
ll ans(0);
ll sum[310000][21], sum2[310000][21];

ll DP(int p, int i){
    if(i == 1)return 1;
    return ((sum[p][i] * sum[p][i] % MOD - sum2[p][i]) % MOD + MOD) % MOD * inv2 % MOD;
}
void dfs(int p = 1, int fa = 0){
    dep[p] = dep[fa] + 1;
    for(auto i = head[p]; i; i = i->nxt)
        if(SON != fa)dfs(SON, p);
}

int main(){
    // freopen("in.txt", "r", stdin);
    // freopen("out.txt", "w", stdout);
    inv2 = qpow(2, MOD - 2);
    N = read();
    if(N == 1)printf("1\n"), exit(0);
    mxdep = (int)log2(N);
    for(int i = 2; i <= N; ++i){
        fa[i] = read();
        head[i] = new Edge{head[i], fa[i]};
        head[fa[i]] = new Edge{head[fa[i]], i};
    }dfs();
    for(int cp = 1; cp <= N; ++cp){
        if(dep[cp] <= mxdep){
            sum[cp][1] = sum2[cp][1] = 1;
            ll cnt(1), cur(cp), lst(-1), lstDP(0);
            while(cur != 1){
                lst = cur;
                cur = fa[cur], ++cnt;
                ll tmp = DP(cur, cnt);
                (((sum[cur][cnt] -= lstDP) %= MOD) += MOD) %= MOD;
                (((sum2[cur][cnt] -= lstDP * lstDP % MOD) %= MOD) += MOD) %= MOD;
                lstDP = tmp;
                (sum[cur][cnt] += DP(lst, cnt - 1)) %= MOD;
                (sum2[cur][cnt] += DP(lst, cnt - 1) * DP(lst, cnt - 1) % MOD) %= MOD;
            }
        }ans = 0;
        for(int i = 1; i <= mxdep; ++i)(ans += DP(1, i)) %= MOD;
        printf("%lld\n", ans);
    }
    fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
    return 0;
}

template < typename T >
inline T read(void){
    T ret(0);
    int flag(1);
    char c = getchar();
    while(c != '-' && !isdigit(c))c = getchar();
    if(c == '-')flag = -1, c = getchar();
    while(isdigit(c)){
        ret *= 10;
        ret += int(c - '0');
        c = getchar();
    }
    ret *= flag;
    return ret;
}

UPD

update-2023_01_03 初稿

posted @ 2023-01-07 15:49  Tsawke  阅读(59)  评论(0编辑  收藏  举报