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} $ 表示矩阵的元素,则对于向下走的下一步的转移比较显然:
对于向右走的下一步也同理可得。
初始值即为 $ 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 $ 的满二叉树拼起来即可,即转移为:
考虑到我们每次询问都是增加一个节点,如此的转移方式复杂度显然是不正确的,考虑优化。不难想到这个式子可以转化为从和平方里去除平方和,即:
于是这个东西就可以支持 $ 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 初稿