AtCoder Beginner Contest 271 题解
AtCoder Beginner Contest 271 Solution
更好的阅读体验戳此进入
题面链接
题面 Luogu 链接
abc 跳了
[ABC271D] Flip and Adjust
题面
给定 $ n $ 张卡片,正面是 $ a_i $,背面是 $ b_i $,给定 $ S $ 要求构造方案钦定这 $ n $ 张卡片选择正面的数或反面的数,使得求和后洽等于 $ S $,无解输出 No
,反之输出 Yes
并输出任意方案。
Solution
一般的思路都是 DP 同时记录决策点然后最后跑一遍答案,不过这样还需要动脑,本题范围较小不如暴力一点,我们直接在 DP 过程中的每一个状态记录所有牌的正反,最开始的思路是开一个 bitset
这样多出来的复杂度是 $ O(\dfrac{n}{w}) $ 也就是 $ O(1) $,不过仔细一想这东西直接开个 basic_string < char >
无脑维护就行了,这样转移会多一个复制的 $ O(n) $,空间也会多一个 $ O(n) $,最终时空复杂度都是 $ O(n^2S) $,刚好 1e8
。
转移显然:
然后中间判断一下是否能转移,也就是 $ dp(i, j) $ 的 $ size $ 是否为 $ i $,以及 $ j - a_i \ge 0 $ 和 $ j - b_i \ge 0 $ 即可。
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 void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
#define ROPNEW_NODE void* Node::operator new(size_t){static Node* P = nd; 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 N, S;
int A[110], B[110];
basic_string < char> dp[110][11000];
int main(){
N = read(), S = read();
for(int i = 1; i <= N; ++i)A[i] = read(), B[i] = read();
dp[1][A[1]] += 'H', dp[1][B[1]] += 'T';
for(int i = 2; i <= N; ++i)
for(int j = 0; j <= S; ++j){
if(j - A[i] >= 0 && (int)dp[i - 1][j - A[i]].size() == i - 1)dp[i][j] = dp[i - 1][j - A[i]] + 'H';
if(j - B[i] >= 0 && (int)dp[i - 1][j - B[i]].size() == i - 1)dp[i][j] = dp[i - 1][j - B[i]] + 'T';
}
if((int)dp[N][S].size() != N)printf("No\n"), exit(0);
printf("Yes\n");
for(auto v : dp[N][S])printf("%c", v);
printf("\n");
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;
}
[ABC271E] Subsequence Path
题面
给定 $ n $ 个点 $ m $ 条边有边权无自环的无向图,给定边集序列 $ E_k $,求边集序列的子序列路径使得能按序从 $ 1 $ 到 $ n $ 且路径长度最小,输出最小值,无解输出 -1
。
Solution
最开始的思路是把边集下放然后每个点挂多个 pair < ll, int >
表示到这个点的所有可能的权值与其对应的最小的序列终止位置,然后用类似二维偏序的思路转移,但是不确定这个思路假没假以及复杂度是否正确,不过细想一下就会发现这题实际上特别水。
不难发现我们要找的是边的子序列,也就是其是有顺序的,那么我们直接按照边序列顺序跑的话显然是无后效性的,所以我们可以按照最短路的思想,考虑记录到每个点的 $ dis $,然后按序列顺序遍历边,用其起始点的 $ dis $ 加上边权去更新该点的 $ dis $,注意到这里我们可以这么做的原因是如果要求前面的边,一定不会在其之前先走后面的边,这也就是我们刚才提到的无后效性,这样最后 $ dis_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 void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
#define ROPNEW_NODE void* Node::operator new(size_t){static Node* P = nd; 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 INFLL (0x3f3f3f3f3f3f3f3fll)
template < typename T = int >
inline T read(void);
int N, M, K;
struct{int s, t, v;}edg[210000];
ll dis[210000];
int main(){
memset(dis, 0x3f, sizeof dis);
N = read(), M = read(), K = read();
for(int i = 1; i <= M; ++i)edg[i].s = read(), edg[i].t = read(), edg[i].v = read();
dis[1] = 0;
for(int i = 1; i <= K; ++i){
int idx = read();
dis[edg[idx].t] = min(dis[edg[idx].t], dis[edg[idx].s] + edg[idx].v);
}printf("%lld\n", dis[N] == INFLL ? -1ll : dis[N]);
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;
}
[ABC271F] XOR on Grid Path
题面
给定一个n行n列的矩阵,定义合法路径为只向右或向下的路径,且途径数字异或和为0。求合法路径条数。
Solution
经典 meet in the middle,记录跑到对角线上的值然后合并即可,复杂度大概是 $ 2 \times 2^{n - 1} $,也就是 $ O(2^n) $,原来的复杂度是 $ O({2n \choose 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 void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
#define ROPNEW_NODE void* Node::operator new(size_t){static Node* P = nd; 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 N;
int A[50][50];
ll ans(0);
unordered_map < int, int > Scnt[50][50], Tcnt[50][50];
void dfs_S(int x = 1, int y = 1, int cur = A[1][1]){
if(x > N || y > N || x + y > N + 1)return;
// printf("S dfs %d %d, cur = %d\n", x, y, cur);
if(x + y == N + 1)Scnt[x][y][cur]++;
dfs_S(x + 1, y, cur ^ A[x + 1][y]);
dfs_S(x, y + 1, cur ^ A[x][y + 1]);
}
void dfs_T(int x = N, int y = N, int cur = A[N][N]){
if(x < 1 || y < 1 || x + y < N + 1)return;
// printf("T dfs %d %d, cur = %d\n", x, y, cur);
if(x + y == N + 1)Tcnt[x][y][cur]++;
dfs_T(x - 1, y, cur ^ A[x - 1][y]);
dfs_T(x, y - 1, cur ^ A[x][y - 1]);
}
int main(){
N = read();
for(int i = 1; i <= N; ++i)for(int j = 1; j <= N; ++j)A[i][j] = read();
dfs_S(), dfs_T();
for(int i = 1; i <= N; ++i){
int X = i, Y = N - i + 1;
for(auto mp : Scnt[X][Y])ans += (ll)mp.second * Tcnt[X][Y][mp.first ^ A[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;
}
[ABC271G] Access Counter
题面
每天存在 $ 24 $ 个时间点,两人要访问一个网站,给定一个 A
和 T
组成的长度为 $ 24 $ 的字符串,若为 T
则 Taka 有 $ X% $ 的概率成功访问,若为 A
则 Aoki 有 $ Y% $ 的概率成功访问,求该网站第 $ n $ 次被访问时是被 Aoki 访问的概率,对 $ 998244353 $ 取模。
Solution
首先看一眼这个题面与 $ n $ 的范围,就不难联想到矩阵快速幂,但是这题的难点我认为主要还是在状态的设计与转移上。
一个显而易见但较为重要的性质,即两次访问之间间隔的天数是易于统计的,重要的在于两次访问的时间点 $ i, j $ 之间的位置关系。
令对应时间点的访问成功概率为 $ p_i $,令一天中未发生任何访问的概率为 $ P = \prod_{i = 1}^{24}(1 - p_i) $,考虑预处理 $ dp(i, j) $ 表示上次在 $ i $ 访问本次在 $ j $ 访问的概率,如果两者之间的距离不超过一天,显然可以预处理,令 $ R $ 为从 $ i + 1 $ 到 $ j - 1 $ 的所有 $ 1 - p $ 之积,则转移显然,即:
显然和式为等比数列求和,且由题意可知公比 $ P \lt 0 $,则继续推式子可以得到:
显然 $ n \to +\infty $ 时 $ P^n \to 0 $,则转化为:
于是我们完成了 $ dp(i, j) $ 的预处理。
令 $ f(i, j) $ 表示第 $ i $ 次访问发生在 $ j $ 时刻的概率,有:
按照我们之前的想法挂到矩阵上:
显然 $ f(0, i) = [i = 24] $,初始时我们默认其最终选择的只能是 $ 24 $,等价于从 $ 0 $ 开始,这样不会对我们的结果造成影响,那么最终形式:
答案统计一下所有 $ i $ 处是 Aoki 的 $ f(n, i) $ 之和即可,可以矩阵快速幂优化,令 $ d = 24 $,则复杂度 $ O(d^3 \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 void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
#define ROPNEW_NODE void* Node::operator new(size_t){static Node* P = nd; 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);
ll N;
ll Taka, Aoki;
ll p[30];
ll P(1);
ll ansv(0);
ll inv100;
ll dp[30][30];
bitset < 30 > belong;
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;
}
class Matrix{
private:
int siz;
ll v[30][30];
public:
Matrix(int len = 24, int pat = 0){
siz = len;
for(int i = 0; i < siz; ++i)
for(int j = 0; j < siz; ++j)
v[i][j] = 0;
switch(pat){
case 1:{
for(int i = 0; i < siz; ++i)v[i][i] = 1;
break;
}
case 2:{
v[0][siz - 1] = 1;
break;
}
case 3:{
for(int i = 1; i <= siz; ++i)
for(int j = 1; j <= siz; ++j)
v[i - 1][j - 1] = dp[i][j];
break;
}
default: break;
}
}
friend Matrix operator * (const Matrix &a, const Matrix &b){
Matrix ret(24);
for(int i = 0; i < 24; ++i)
for(int j = 0; j < 24; ++j)
for(int k = 0; k < 24; ++k)
(ret.v[i][j] += a.v[i][k] * b.v[k][j] % MOD) %= MOD;
return ret;
}
void SetAns(void){
for(int i = 0; i < 24; ++i)
if(belong[i + 1])(ansv += v[0][i]) %= MOD;
}
void Print(void){
printf("##########\n");
for(int i = 0; i < siz; ++i)
for(int j = 0; j < siz; ++j)
printf("%lld%c", v[i][j], j == siz - 1 ? '\n' : ' ');
printf("##########\n");
}
}ans(24, 2);
Matrix qpow(Matrix a, ll b){
Matrix ret(24, 1), mul(a);
while(b){
if(b & 1)ret = ret * mul;
b >>= 1;
mul = mul * mul;
}return ret;
}
int main(){
inv100 = qpow(100, MOD - 2);
N = read < ll >(), Taka = read(), Aoki = read();
for(int i = 1; i <= 24; ++i){
char c = getchar(); while(!isalpha(c))c = getchar();
p[i] = c == 'T' ? Taka : Aoki, belong[i] = c == 'T' ? false : true;
(P *= (100 - p[i]) * inv100 % MOD) %= MOD;
}
for(int i = 1; i <= 24; ++i){
for(int j = 1; j <= 24; ++j){
ll R(1);
if(i < j)for(int k = i + 1; k <= j - 1; ++k)(R *= (100 - p[k]) * inv100 % MOD) %= MOD;
else{
for(int k = i + 1; k <= 24; ++k)(R *= (100 - p[k]) * inv100 % MOD) %= MOD;
for(int k = 1; k <= j - 1; ++k)(R *= (100 - p[k]) * inv100 % MOD) %= MOD;
}
dp[i][j] = R * (p[j] * inv100 % MOD) % MOD * qpow((1 - P + MOD) % MOD, MOD - 2) % MOD;
}
}
Matrix base(24, 3);
(ans * qpow(base, N)).SetAns();
printf("%lld\n", ansv);
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;
}
[ABC271Ex] General General
题面
给定终点 $ (A, B) $,存在向量集合 $ S = { (1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0), (-1, -1), (0, -1), (1, -1) } $,每次给定一个长度为 $ 8 $ 的 $ 01 $ 串表示对应的向量是否可以被使用且无限次使用,你需要用可以使用的向量从起点 $ (0, 0) $ 到达终点 $ (A, B) $,最小化使用的向量的个数,输出最小值,无解输出 -1
。
Solution
首先这道题应该很明显是分类讨论,但是找到的性质的多少也就决定了式子最终的复杂程度。
首先这八个向量分别对应了右、右上、上、$ \cdots $、右下,也就是逆时针顺序的八向移动,这里我们称右、上、左、下为四向向量,右上、左上、左下、右下为八向向量。
首先我们想到,若终点可以被一个向量表示,那么一定可以只用这个向量表示(废话)。
其次一个显而易见的性质,显然两个相邻的四向向量可以表示出其夹着的八向向量(但贡献会多 $ 1 $)两个八向向量可以表示出其夹着的四向向量(贡献不变),而这个性质普遍地讲可以认为就是平面向量基本定理,也就是平面任意向量都可以用两个不共线向量的 $ a\vec{x} + b\vec{y} $ 表示,但是这里显然我们需要要求 $ a, b $ 非负且为整数,对于非负可以转化为向量只能表示出其夹角内的所有向量,整数则需要具体讨论一下了。
所以不难想到,如果一个向量可以被任意两个向量表示,那么就变成了一个简单的解方程的问题,而若解出来的解全都不合法(有负数或者不为整数),就说明这个向量无法被仅用两个向量表示。
而这里我们可以思考一下,对于某个象限内的终点,若我们可以用其对应的两个四向向量,那么是一定可以表示出来的,所以无法表示就说明我们一定不是有对应的两个四向向量的情况。
而如果是选择的一个四向向量和一个八向向量,那么我们再选一个四向向量上去是没意义的,因为都可以更优地被之前的一个四向向量和一个八向向量表示,所以我们可以考虑一下再选一个八向向量的情况,这个时候我们发现,若这个四向向量用了达到两次,那么就可以在不增加贡献的情况下用另外两个八向向量表示,所以对于这个情况我们可以转化为使用一个四向向量后再用另外两个八向向量解方程。
而对于初始选择两个八向向量在选择一个四向向量的情况与上一种情况本质相同。
同时我们继续思考就会发现再没有更多情况了。
于是我们现在梳理一下共有哪些可能:
- 用一个向量表示。
- 用两个向量表示(应该是两个不共线的向量,但是范围较小这里无需剪枝,可以强行枚举)。
- 用一个四向向量且只使用一次,和任意两个相邻的八向向量表示。
令种类数 $ d = 8 $,复杂度大概是 $ O(d^2T) $ 的,可以通过。
Tips:上文很多部分的再选一个等语言可能已经默认了额外选择的是相邻的或夹着的,因为其它部分情况的错误性过于显然,故不赘述了。
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 void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
#define ROPNEW_NODE void* Node::operator new(size_t){static Node* P = nd; 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 EPS (1e-10)
#define INFLL (0x3f3f3f3f3f3f3f3fll)
template < typename T = int >
inline T read(void);
ll A, B;
ll ans(INFLL);
ll dx[10] = {0, 1, 1, 0, -1, -1, -1, 0, 1};
ll dy[10] = {0, 0, 1, 1, 1, 0, -1, -1, -1};
bitset < 10 > exists;
bool isInteger(ld v){
return fabs(ld(ll(v)) - v) < EPS;
}
void Check(int i, int j, int base = 0){
if(dx[i] * dy[j] - dx[j] * dy[i] == 0)return;
ld v1 = (ld)(B * dx[i] - A * dy[i]) / (dx[i] * dy[j] - dx[j] * dy[i]);
if(v1 <= -EPS || !isInteger(v1))return;
ld v2 = (ld)(B * dx[j] - A * dy[j]) / (dx[j] * dy[i] - dx[i] * dy[j]);
if(v2 <= -EPS || !isInteger(v2))return;
ans = min(ans, ll(v1) + ll(v2) + base);
}
int main(){
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
int T = read();
while(T--){
A = read(), B = read();
for(int i = 1; i <= 8; ++i){
char c = getchar(); while(!isdigit(c))c = getchar();
exists[i] = c == '1';
}ans = INFLL;
if(!A && !B){printf("0\n"); continue;}
for(int i = 1; i <= 8; ++i)
if(exists[i]){
if(A * dx[i] < 0 || B * dy[i] < 0)continue;
if((!dx[i] && (A || !isInteger((ld)B / dy[i]))) || (!dy[i] && (B || !isInteger((ld)A / dx[i]))))continue;
if(!dx[i])ans = min(ans, B / dy[i]);
if(!dy[i])ans = min(ans, A / dx[i]);
ld v1 = (ld)A / dx[i], v2 = (ld)B / dy[i];
if(!isInteger(v1) || !isInteger(v2) || (ll)v1 != (ll)v2)continue;
ans = min(ans, (ll)v1);
}
for(int i = 1; i <= 8; ++i)for(int j = i + 1; j <= 8; ++j)
if(exists[i] && exists[j])Check(i, j);
for(int i = 2; i <= 8; i += 2){
if(!exists[i] || !exists[i == 2 ? 8 : i - 2])continue;
for(int j = 1; j <= 8; j += 2){
if(!exists[j])continue;
A -= dx[j], B -= dy[j];
Check(i == 2 ? 8 : i - 2, i, 1);
A += dx[j], B += dy[j];
}
}
printf("%lld\n", ans == INFLL ? -1ll : 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_02_09 初稿