区域赛铜牌题题单寒假纪

前言

刷刷某大佬整理的区域赛铜牌题题单 (十分之感谢!)

记录一下知识,顺便梳理思路

BA - BZ

BA - The Only Way to the Destination

难度: 2.5

问,任意两方块是否有唯一的简单路径。

思路

首先特判 n = 1 的情况,一定满足,一开始忘判了

n >= 2,发现存在 2 × 2 的方格就不能满足条件,而由于墙的列数不会很多(k ≤ 1e5),所以 m ≤ 1e9 其实是诈骗,m > 3k 就肯定会有并列的两个空行,一定无法满足条件。此外,可以想象,若存在某个墙组成的连通块不和边界相连,则一定是因为某个路径将其包围,形成了一个环,这种情况一定不满足。

若合法,则一定有 m < 3k,于是我们可以枚举列,判断是否存在 2 × 2 的情况。判断墙是否和边界连通,可以对墙建图后 dfs

整体感觉不是很好写,比较考验码力

另外还要注意,墙是 8 连通而不是 4 连通

MYCODE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

constexpr int inf = 0x3f3f3f3f;

void solve(){
int n, m, k;
cin >> n >> m >> k;

// 边界
if(n == 1){
int x; while(cin>>x);
cout << "YES" << '\n';
return;
}
if(m > 3*k){
int x; while(cin>>x);
cout << "NO" << '\n';
return;
}

vector<vector<pii>> col(m + 2);
for(int i=1; i<=k; i++){
int l, r, y;
cin >> l >> r >> y;
col[y].push_back({l, r});
}
col[0].push_back({1, n});
col[m+1].push_back({1, n});
for(int i=0; i<=m+1; i++){
col[i].push_back({0, 0});
col[i].push_back({n+1, n+1});
sort(col[i].begin(), col[i].end());
}
bool ok = true;
for(int i=1; i<=m; i++){
int p1 = 1, p2 = 1;
while(p1 < col[i-1].size()){ // (l, r) 左右都开, 边界需要处理一下
while(p2 < col[i].size()-1 && col[i-1][p1-1].second+1 > col[i][p2].first-1) p2++;
while(p2 < col[i].size() && col[i-1][p1].first-1 >= col[i][p2-1].second+1){
int l = max(col[i-1][p1-1].second+1, col[i][p2-1].second+1);
int r = min(col[i-1][p1].first-1, col[i][p2].first-1);
if(r - l + 1 > 1) ok = false;
p2++;
}
p2--;
p1++;
}
}

// 连通性判的不对 , 还是不对 , 墙是八连通
map<pii, int> idx; // {i, p}
vector<vector<int>> gra(k + 1);
int tot = 0;
for(int i=1; i<=m; i++){
for(int j=1; j<col[i].size()-1; j++){
idx[{i, j}] = ++tot;
}
}
for(int i=2; i<=m; i++){
int p = 1;
for(int j=1; j<col[i].size()-1; j++){
auto [l, r] = col[i][j];
while(col[i-1][p].second+1 < l) p++;
while(p < col[i-1].size()-1 && col[i-1][p].first-1 <= r){
int u = idx[{i, j}], v = idx[{i-1, p}];
// cout << u << ' ' << v << endl; //
gra[u].push_back(v);
gra[v].push_back(u);
p++;
}
p--;
}
}
for(int i=1; i<=m; i++){
for(int j=2; j<col[i].size()-1; j++){
if(col[i][j-1].second+1 == col[i][j].first){
int u = idx[{i, j}], v = idx[{i, j-1}];
gra[u].push_back(v);
gra[v].push_back(u);
}
}
}
vector<bool> vis(k + 1);
auto dfs = [&](auto&& self, int u) ->void {
if(vis[u]) return;
vis[u] = true;
for(int v : gra[u]){
self(self, v);
}
};
vector<int> a;
for(int i=1; i<col[1].size()-1; i++) a.push_back(idx[{1, i}]);
for(int i=1; i<col[m].size()-1; i++) a.push_back(idx[{m, i}]);
for(int i=1; i<=m; i++){
for(int j=1; j<col[i].size()-1; j++){
auto[l, r] = col[i][j];
if(l == 1 || r == n){
a.push_back({idx[{i, j}]});
}
}
}
for(auto u : a){
dfs(dfs, u);
}
// cout << count(vis.begin()+1, vis.end(), true) << endl;
ok &= (count(vis.begin()+1, vis.end(), true) == k);
cout << (ok? "YES":"NO") << '\n';
}

int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);

int t = 1; //cin >> t;
while(t--) solve();

return 0;
}

BB - Gene

怎么说呢,完全没思路,直接看答案了

难度: ?

q 次询问, 每次给你字符串 t,若 x 为 si 和 t 字符不相同的位置个数,问有几个 s 满足 x ≤ k

(1 ≤ N, Q ≤ 300, 1 ≤ M ≤ 60, 000,1 ≤ K ≤ 10) x

思路

思路还是很好懂的,就是对字母二进制化后用 bitset/ull 进行优化。具体来说是每个字母用 5 位二进制表示 (25 > 26),5 位全相等则这个字符相等,每次可以判断 64 个字符

也可以进行剪枝优化,因为 k 很小,比较容易超过

时间复杂度

MYCODE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;

constexpr int inf = 0x3f3f3f3f;

// bitset/ull 优化
// 26 - 5bit
void solve(){
int n, q, m, k;
cin >> n >> q >> m >> k;

int z = (m+63) / 64;
// 第 i 个字符串 , 第 j 位 , 第 k 个字符
vector<vector<vector<ull>>> bit(n, vector<vector<ull>>(5, vector<ull>(z)));
for(int i=0; i<n; i++){
string s; cin >> s;
int p = 0;
while(p < m){
int mask = s[p] - 'a';
for(int j=0; j<5; j++){
bit[i][j][p/64] |= (ull)(mask>>j&1) << (p%64);
}
p++;
}
}
for(int qq=0; qq<q; qq++){
string t; cin >> t;
vector<vector<ull>> cur(5, vector<ull>(z));
int p = 0;
while(p < m){
int mask = t[p] - 'a';
for(int j=0; j<5; j++){
cur[j][p/64] |= (ull)(mask>>j&1) << (p%64);
}
p++;
}
int ans = 0;
for(int i=0; i<n; i++){
auto& vec = bit[i];
int x = 0;
for(int j=0; j<z; j++){
ull mask = 0;
for(int p=0; p<5; p++){
mask |= (vec[p][j] ^ cur[p][j]);
}
x += __builtin_popcountll(mask);
// cout << x << endl;
if(x > k) break;
}
if(x > k) continue;
ans++;
}
cout << ans << '\n';
}
}

int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);

int t = 1; //cin >> t;
while(t--) solve();

return 0;
}

BC - Indeterminate Equation

难度: 2.5

ak − bk = n 有几个解

(T ≤ 10, n ≤ 1e18, 3 ≤ k ≤ 64)

思路

分类讨论,可以算出 k = 4 时,大概 a = 5e7 时就有 ak − (a − 1)k > 1e18k > 4 时只会更少,所以可以暴力枚举 b,然后直接计算 a

k = 3 时,直接枚举 b 不可行。由 (a − b)3 = a3 − b3 + 3ab2 − 3a2b 可以得到,(a − b)3 = n + 3ab2 − 3a2b,可以发现 a-b 的值是可以枚举的,最多也不会超过 2e6 的级别。令 Δ = a − b 化简可得一元二次方程,。对称轴两边分别二分即可解出一元二次方程的两根。

MYCODE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;
using pii = pair<int, int>;
using i128 = __int128;

constexpr int inf = 0x3f3f3f3f;

void solve(){
ll n; int k;
cin >> n >> k;
int ans = 0;
if(k == 3){
for(i128 i = 1; i * i * i < n; i++){
if((n - i*i*i) % (3*i)) continue;
double t = i / (-2);
auto f = [&](auto x){
return x*x + i*x - (n-i*i*i) / (3*i);
};
bool ok = false;
{
double lo = -1e15, hi = t;
while(hi - lo > 1e-5){
double mid = (lo + hi) / 2;
if(f(mid) >= 0) lo = mid;
else hi = mid;
}
ok |= (f((i128)lo)==0);
}
{
double lo = t, hi = 1e15;
while(hi - lo > 1e-5){
double mid = (lo + hi) / 2;
if(f(mid) >= 0) hi = mid;
else hi = mid;
}
ok |= (f((i128)hi)==0);
}
ans += ok;
}
}
else if(k > 3){
// b = 1
int x = 2;
while( 1 ){
i128 a = 1;
for(int i=0; i<k; i++){
if(a > n+1) break;
a *= x;
}
if(a > n+1) break;
if(a == n+1) ans++;
x++;
}

auto check = [&](int base, auto aim){
i128 a = 1;
for(int z=0; z<k; z++){
a *= base;
if(a >= aim) return true;
}
return false;
};

// b >= 2
x = 2;
{
int lo = 1, hi = 7e5;
while(lo + 1 != hi){
int mid = lo + hi >> 1;
if(check(mid, n)) hi = mid;
else lo = mid;
}
x = lo;
}
i128 a = 1, lst = 1;
for(int z=0; z<k; z++) a*=x;
lst = a;
for(int i=2; i<=700000; i++){
i128 b = 1;
for(int z=0; z<k; z++) b*=i;
while(a < b+n){
lst = a;
x++;
a = 1;
for(int z=0; z<k; z++) a*=x;
}
if(a - lst > n) break;
ans += (a-b==n);
}
}
cout << ans << '\n';
}

int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);

int t = 1; cin >> t;
while(t--) solve();

return 0;
}

BD - Rooted Tree

难度: 3

一开始只有一个节点,深度为 0,每次等概率的选一个叶子节点,给这个叶子节点新添 m 个节点,问 k 次操作后所有节点的深度和期望是什么

(k ≤ 1e7)

思路

错误思路:

可以设不同深度的节点个数分别为 x0, x1, x2...,可以发现这样每次推下一层都是 O(n) 的,且式子很难优化,设法不是很好

正解:

首先可以发现每次操作后叶子节点个数是可算的,令 li 为第 i 次操作后的叶子个数 然后令 fi 为叶子节点的深度和 (也就是不需要把每个深度的叶子个数都设出来),可以得到递推式

其中 为第 i 次操作后的平均叶子深度

答案就为

tip:此题求逆元可以前缀积优化到线性

具体来说

只需求出 prek 即可线性逆推

MYCODE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;
using pii = pair<int, int>;

constexpr int inf = 0x3f3f3f3f;
constexpr int mo = 1e9 + 9;

ll qpow(ll a, ll x){
ll ans = 1;
while(x){
if(x & 1) ans *= a;
a *= a;
x >>= 1;
ans %= mo;
a %= mo;
}
return ans;
}
int inv(int x){
return qpow(x, mo-2);
}

void solve(){
int m, k;
cin >> m >> k;
int ans = 0;
int l = 1, f = 0; // 叶子数 , 叶子期望和
vector<int> pre(k, 1);
for(int i=1; i<k; i++){
pre[i] = 1 + i*(m-1);
pre[i] = 1ll * pre[i-1] * pre[i] % mo;
}
int invp = inv(pre[k-1]);
vector<int> in(k + 1, 1);
for(int i=k-1; i>=1; i--){
in[i] = 1ll * invp * pre[i-1] % mo;
invp = 1ll * invp * (1 + (-1+m)*i) % mo;
}

for(int i=0; i<k; i++){
ans += 1ll * m * (1ll * f * in[i] % mo + 1) % mo;
ans %= mo;
f = f + 1ll * f * in[i] % mo * (m - 1) % mo + m;
f %= mo;
}
cout << ans << '\n';
}

int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);

int t = 1; //cin >> t;
while(t--) solve();

return 0;
}

BE - Tavern Chess

难度: 2

炉石,每次最左攻击次数最少的随从开始发起攻击,等概率攻击对方随从,两人交替发起攻击,问 Alice 赢,输,平 的概率分别是多少

(n, m ≤ 7)

思路

每次攻击至少有一个随从死亡且 n, m 很小,直接 dfs 暴力模拟即可

MYCODE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <bits/stdc++.h>
using namespace std;

constexpr int inf = 0x3f3f3f3f;

void solve(){
int on, om;
cin >> on >> om;
vector<int> oa(on), ob(om);
for(auto& x : oa) cin >> x;
for(auto& x : ob) cin >> x;
vector<array<int, 3>> ta(on), tb(om); // hp atk cnt
for(int i=0; i<on; i++) ta[i] = {oa[i], oa[i], 0};
for(int i=0; i<om; i++) tb[i] = {ob[i], ob[i], 0};
double ans1 = 0, ans2 = 0, ans3 = 0;
auto dfs = [&](auto&& self, auto a, auto b, double p, bool flg) ->void {
if(a.empty() && b.empty()) ans3 += p;
else if(a.empty()) ans2 += p;
else if(b.empty()) ans1 += p;
if(a.empty() || b.empty()) return;
int n = a.size(), m = b.size();
// a atk cnt
if(flg){
// 找 cnt mi
int idx = 0, mi = inf;
for(int i=0; i<n; i++){
if(a[i][2] < mi){
idx = i;
mi = a[i][2];
}
}
//
for(int i=0; i<m; i++){
auto na = a, nb = b;
na[idx][2]++;
na[idx][0] -= nb[i][1];
nb[i][0] -= na[idx][1];
if(na[idx][0] <= 0) na.erase(na.begin()+idx);
if(nb[i][0] <= 0) nb.erase(nb.begin()+i);
self(self, na, nb, p/m, flg^1);
}
}
else{
int idx = 0, mi = inf;
for(int i=0; i<m; i++){
if(b[i][2] < mi){
idx = i;
mi = b[i][2];
}
}
for(int i=0; i<n; i++){
auto na = a, nb = b;
nb[idx][2]++;
nb[idx][0] -= na[i][1];
na[i][0] -= nb[idx][1];
if(nb[idx][0] <= 0) nb.erase(nb.begin()+idx);
if(na[i][0] <= 0) na.erase(na.begin()+i);
self(self, na, nb, p/n, flg^1);
}
}
};
if(on == om){
dfs(dfs, ta, tb, 0.5, true);
dfs(dfs, ta, tb, 0.5, false);
}
else dfs(dfs, ta, tb, 1, on>om);
cout << fixed << setprecision(15) << ans1 << ' ' << ans2 << ' ' << ans3 << '\n';

}

int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);

int t = 1; //cin >> t;
while(t--) solve();

return 0;
}

BG - Find Maximum

难度:2.5

找规律&数学,是个好题,要仔细思考

思路

先鸽一会儿

MYCODE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

constexpr int inf = 0x3f3f3f3f;

void solve(){
ll l, r;
cin >> l >> r;
auto calc = [&](auto self, ll x) ->int {
if(x == 0) return 1;
if(x % 3 == 0) return 1 + self(self, x/3);
return x%3 + self(self, x-x%3);
};
int ans = 0;
ans = max(ans, calc(calc, r));
if(r-1>=l) ans = max(ans, calc(calc, r-1));
if(r-2>=l) ans = max(ans, calc(calc, r-2));
// 上层
{
ll x = 1;
while(x <= r) x *= 3;
x /= 3; // 和 r 同层第一个数
if(l <= x-1 && x-1 <= r) ans = max(ans, calc(calc, x-1));
}
// 同层
{
ll x = 3;
while(x <= r){
ll t = r;
t /= x, t *= x;
t--;
if(t < l) break;
ans = max(ans, calc(calc, t));
x *= 3;
}
}

cout << ans << '\n';
}

int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);

int t = 1; cin >> t;
while(t--) solve();

return 0;
}

BH

难度: 2

很久以前在机房写的

思路

若只考虑第一种集合,可以发现所需集合数就是叶子节点个数。然后我们此时考虑第二种集合,最优肯定是删掉最下面的一层叶子。枚举删了几层叶子即可。

MYCODE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
using ll = long long;
const int N=1e5+9;
const int inf=3e18+9;

void run(){
int n;
cin >> n;
vector<int> p(n + 1);
vector<vector<int>> gra(n + 1);
for(int i=2; i<=n; i++){
int x;
cin >> x;
gra[x].push_back(i);
p[i] = x;
}

int ans = inf;
vector<int> del(n + 1);
vector<int> leaf;
for(int i=1; i<=n; i++){
if(gra[i].size() == 0){
leaf.push_back(i);
}
}
ans = leaf.size();

int cost = 0;
while(!leaf.empty()){
vector<int> nxt;
for(int x : leaf){
del[p[x]]++;
if(p[x] && del[p[x]] == gra[p[x]].size()){
nxt.push_back(p[x]);
}
}
leaf = nxt;
cost++;
ans = min(ans, cost + (ll)leaf.size());
}
cout << ans << '\n';

}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int t=1;
cin>>t;
for(;t;t--) run();
}

BI

给你 n 个数,要求变成全相等。有三种操作,+1,-1,/2,花费都是 1,求最小花费

难度: 2.5

思路:

注意到了一定是先除2,若不考虑除2操作,则是仓库选址的问题。若已经进行了若干次除2操作,最终的目标数是确定的,是中位数。注意到这个可能的中位数只有 nlog 个,我们可以枚举这个可能的中位数作为目标数,然后枚举计算每个元素变成目标数的最小花费,去掉 m 个较大的即可

MYCODE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

constexpr int inf = 0x3f3f3f3f;

void solve(){
int n, m;
cin >> n >> m;
vector<int> a(n + 1);
for(int i=1; i<=n; i++) cin >> a[i];
ll ans = inf;
for(int i=1; i<=n; i++){
for(int j=0; j<=31; j++){
vector<int> cost;
for(int k=1; k<=n; k++){
int mi = inf;
for(int l=0; l<=31; l++){
mi = min(mi, l + abs((a[i] >> j) - (a[k] >> l)));
}
cost.push_back(mi);
}
ll sum = 0;
sort(cost.begin(), cost.end());
for(int k=0; k<n-m; k++){
sum += cost[k];
}
ans = min(ans, sum);
}
}
cout << ans << '\n';
}

int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);

int t = 1; cin >> t;
while(t--) solve();

return 0;
}

BJ - Frozen Scoreboard

难度: 2

思路

题面略长,思路简单,只需要考虑 ? 的情况,m ≤ 13 可以状压枚举某道题过没过,然后贪心计算即可

MYCODE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using pii = pair<int, int>;

constexpr int inf = 0x3f3f3f3f;

void solve(){
int n, m;
cin >> n >> m;
for(int nn=0; nn<n; nn++){
int sol, tot;
cin >> sol >> tot;
vector<string> ans(m);
vector<pii> a(m);
int S = 0;
for(int i=0; i<m; i++){
char tp; cin >> tp;
if(tp == '+'){
string s; cin >> s;
ans[i] = "+ " + s;
// cout << *(find(s.begin(), s.end(), '/')) << endl; //
int x = stoi(string(s.begin(), find(s.begin(), s.end(), '/')));
int y = stoi(string(find(s.begin(), s.end(), '/')+1, s.end()));
tot -= (x - 1) * 20;
tot -= y;
sol--;
}
if(tp == '-'){
int x; cin >> x;
ans[i] = "- " + to_string(x);
}
if(tp == '?'){
int x, y;
cin >> x >> y;
a[i] = {x, y};
S += (1 << i);
}
if(tp == '.') ans[i] = ".";
}
// cout << tot << endl; //
if(tot < 0){
cout << "No" << '\n';
continue;
}
if(sol == 0 && tot == 0){ // 封榜后没过题
for(int p=0; p<m; p++) if(S >> p & 1) {
auto [x, y] = a[p]; // x/y
ans[p] = "- " + to_string(y);
}
cout << "Yes" << '\n';
for(string s : ans) cout << s << '\n';
continue;
}

bool ok = false;
for(int T=S; T; T=(T-1)&S){ // 封榜后至少过 1 题
if(__builtin_popcount(T) != sol) continue;
int tm = tot;
for(int p=0; p<m; p++){
auto [x, y] = a[p]; // x/y
if(S >> p & 1) ans[p] = "- " + to_string(y);
if(!(T >> p & 1)) continue;
tm -= (y-x) * 20;
tm -= 240;
}
if(tm < 0) continue;
for(int p=0; p<m; p++){
if(!(T >> p & 1)) continue;
auto [x, y] = a[p]; // x/y
int cnt = 0; // 用了几次罚时
int fn = 240; // 过题时间
{
int z = min(tm / 20, x-1);
tm -= 20*z, cnt += z;
}
{
int z = min(tm, 59);
tm -= z, fn += z;
}
ans[p] = "+ " + to_string(y-x + cnt + 1) + "/" + to_string(fn);
}
if(tm != 0) continue;
ok = true;
cout << "Yes" << '\n';
for(string s : ans) cout << s << '\n';
break;
}
if(!ok) cout << "No" << '\n';
}
}

int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);

int t = 1; //cin >> t;
while(t--) solve();

return 0;
}

BK - Modulo Ruins the Legend

难度: 2.5

数学,线性同余方程,裴蜀定理,exgcd

线性同余方程ax ≡ b (mod  m)

x ∈ [0, m) 时,。另外还有 g|b

逆元。qpow(x, mod-2) 仅能求质数模数逆元,exgcd 可求任意模数逆元。另外逆元存在的前提条件是 gcd(x, mod) = 1

exgcd 可以求出 ax + by = g 的一组特解,若 a = k1g, b = k2g,则 ax, by 之间的最小调整量是 k1k2g

思路

题目求 ,即求 min{ax + by + c}

令 g = gcd(a, b), exgcd 求出 ax + by = g 的一组特解 x0, y0

转化成 min{gx + c},由于 gx 的值域是 k ⋅ gcd(g, m) modm,所以最小值

解出 gx + c ≡ mi (mod  m),设解为 f,则 ax + by + c ≡ mi (mod  m) 的解为 fx0, fy0

MYCODE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

constexpr int inf = 0x3f3f3f3f;
int p = -1;

void exgcd(int a, int b, int& x, int& y){
if(b == 0){
x = 1, y = 0;
return;
}
int x1, y1;
exgcd(b, a % b, x1, y1);
x = y1;
y = x1 - (a / b) * y1;
}

// p任意 , gcd(a, p) != 1 则无解
ll inv(int a, int mod){
int x, y;
exgcd(a, mod, x, y);
int g = a*x + mod*y;
return g == 1 ? (x % mod + mod) % mod : -1;
}

// 逆元不能 mo-2 求, 因为 p 任意
void solve(){
int n;
cin >> n >> p;
int z = 0;
for(int i=0; i<n; i++){
int x; cin >> x;
z += x;
z %= p;
}
int a = n % p, b = 1ll * n * (n + 1) / 2 % p;
int x, y;
exgcd(a, b, x, y);
// cout << x << ' ' << y << '\n';
int g = a*x + b*y;
int cnt = (p - z + gcd(g, p)-1) / gcd(g, p);
int res = (z + cnt*gcd(g, p)) % p;
int f = 1ll * cnt * inv(g/gcd(g, p), (p/gcd(g, p))) % (p/gcd(g, p));
x = (1ll * f * x % p + p) % p, y = (1ll * f * y % p + p) % p;
cout << res << '\n';
cout << x << ' ' << y << '\n';
}

int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);

int t = 1; //cin >> t;
while(t--) solve();

return 0;
}

BL - No Bug No Game

难度: 2

思路

01背包。最多只有一个物品是部分使用的,可以分成两层,dp0 代表选的物品中没有部分使用的物品,dp1 代表选的物品中有部分使用的物品,dp0 可以转移到 dp1,另外需要注意,最后取 dp0 所有状态最值和 dp1 的满容量状态最值

细节上就是注意初始化成 -inf,否则会导致 dp1 的计算出现问题,会导致不合法状态

MYCODE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

constexpr int inf = 0x3f3f3f3f;

// 初始 - inf 才是完全严格
void solve(){
int n, m;
cin >> n >> m;
vector<int> sz(n + 1);
vector<vector<int>> w(n + 1);
for(int i=1; i<=n; i++){
cin >> sz[i];
w[i].resize(sz[i] + 1);
for(int j=1; j<=sz[i]; j++){
cin >> w[i][j];
}
}
int ma = 0;
vector<vector<int>> dp0(n + 1, vector<int>(m + 1, -inf)), dp1 = dp0;
dp0[0][0] = 0;
for(int i=1; i<=n; i++){
for(int j=0; j<=m; j++){
dp0[i][j] = max(dp0[i][j], dp0[i-1][j]);
dp1[i][j] = max(dp1[i][j], dp1[i-1][j]);
if(j >= sz[i]){
dp0[i][j] = max(dp0[i][j], dp0[i-1][j-sz[i]] + w[i][sz[i]]);
}
if(j >= sz[i]){
dp1[i][j] = max(dp1[i][j], dp1[i-1][j-sz[i]] + w[i][sz[i]]);
}
ma = max(ma, dp0[i][j]);
}
for(int j=0; j<=m; j++){
for(int k=1; k<=sz[i]; k++){
if(j >= k){
dp1[i][j] = max(dp1[i][j], dp0[i-1][j-k] + w[i][k]);
}
}
}
}
cout << max(ma, dp1[n][m]) << '\n';
}

int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);

int t = 1; //cin >> t;
while(t--) solve();

return 0;
}

BM Master of Both

难度: 2

给你 n 个字符串,按照给定的字典序比较,q 次询问,每次给定一个字典序,求这 n 个字符串逆序对的数量

(n ≤ 5e5, q ≤ 5e4)

思路

字典树,预处理

两个字符串比较,取决于第一个不相等的字符,我们只需要记录关键字符对 x-y 的数量,比如说 abc 和 abd,只需记录 c-d 字符对

每次询问 O(262) 枚举给定的字典序即可

MYCODE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

void solve(){
int n, q;
cin >> n >> q;

int tot = 0;
vector<map<char, int>> kid(1);
vector<int> path(1), end(1); // 路径数, 终点个数
vector<vector<ll>> cnt(26, vector<ll>(26)); // x-y 对
ll pre = 0; // 前缀逆序
auto insert = [&](string s) ->void {
int x = 0;
path[x]++;
for(int i=0; i<s.size(); i++){
char c = s[i];
if(!kid[x].count(c)){
kid[x][c] = ++tot;
kid.push_back({});
path.push_back({});
end.push_back({});
}
for(int i=0; i<26; i++){
if(kid[x].count('a'+i)){
cnt[i][c-'a'] += path[kid[x][i+'a']];
}
}
x = kid[x][c];
path[x]++;
if(i == s.size()-1) end[x]++;
}
// pre += path[x] - 1; // 可能同一点终止
pre += path[x] - end[x];
};
for(int i=0; i<n; i++){
string s;
cin >> s;
insert(s);
}

for(int qq=0; qq<q; qq++){
string s; cin >> s;
ll ans = pre;
for(int i=0; i<26; i++){
for(int j=i+1; j<26; j++){
ans += cnt[s[j]-'a'][s[i]-'a'];
}
}
cout << ans << '\n';
}
}

int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);

int t = 1; //cin >> t;
while(t--) solve();

return 0;
}

BY - Infection

期望 树上背包合并

难度: 3

选一个点为根,每个点被选中的概率为 ,不断向外染色,每个点被染色成功的概率是 pi,计算恰好 k 个节点被染色的概率,需要计算所有 k=1,2…n 的答案

思路

最后形成的一定是一个连通块。dp 来解决连通块相关的计数问题。

对于一个连通块 S,它的生成概率为

将此概率拆成两部分维护,令 dp[i][j] = {prod, val} ,代表连通块最上面的点是 i,当前形成的连通块大小为 j。

但是不包括 i 的父亲的 1 − pfa,这个放到最后去乘

dp 状态转移

对于两个相邻且无重叠连通块 S1, S2,合并答案,则

prod3 = prod1 × prod2

val3 = val1prod2 + val2prod1

之后进行树上背包合并就好了,时间复杂度 O(n2)

val 的转移公式推导

把前面的一坨简记为 ,后面的一坨简记为 p

MYCODE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;
using pii = pair<int, int>;

constexpr int inf = 0x3f3f3f3f;
constexpr int mo = 1e9 + 7;

ll qpow(ll a, ll x){
ll ans = 1;
while(x){
if(x & 1) ans *= a;
a *= a;
x >>= 1;
ans %= mo;
a %= mo;
}
return ans;
}
ll inv(int x){
return qpow(x, mo - 2);
}

struct Node{
ll prod;
ll val;
};

// 期望, 树上背包合并
void solve(){
int n;
cin >> n;
vector<vector<int>> gra(n + 1);
for(int i=0; i<n-1; i++){
int u, v;
cin >> u >> v;
gra[u].push_back(v);
gra[v].push_back(u);
}

ll sum = 0;
vector<int> a(n + 1), p(n + 1);
for(int i=1; i<=n; i++){
int b, c;
cin >> a[i] >> b >> c;
sum += a[i]; sum %= mo;
p[i] = b * inv(c) % mo;
}

ll isum = inv(sum);
vector<int> sz(n + 1);
vector<ll> ans(n + 1);
vector<vector<Node>> dp(n + 1, vector<Node>(n + 1));
auto dfs = [&](auto&& self, int fa, int u) -> void {
sz[u] = 1;
dp[u][1].prod = p[u];
dp[u][1].val = a[u] * isum % mo;
dp[u][0].prod = (1 - p[u] + mo) % mo; // 断点
dp[u][0].val = 0;
for(int v : gra[u]) if(v != fa) {
self(self, u, v);
vector<Node> ndp(n + 1, {0, 0});
ndp[0] = dp[u][0];
for(int i=1; i<=sz[u]; i++){
for(int j=0; j<=sz[v]; j++){
ndp[i+j].prod += dp[u][i].prod * dp[v][j].prod % mo;
ndp[i+j].val += (dp[u][i].val * dp[v][j].prod + dp[u][i].prod * dp[v][j].val) % mo;
ndp[i+j].prod %= mo; ndp[i+j].val %= mo;
}
}
sz[u] += sz[v];
swap(dp[u], ndp);
}
for(int i=1; i<=sz[u]; i++){
ans[i] += dp[u][i].val * (fa==0? 1:(1-p[fa]+mo)%mo) % mo;
ans[i] %= mo;
}
};
dfs(dfs, 0, 1);
for(int i=1; i<=n; i++){
cout << ans[i] << '\n';
}
}

int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);

int t = 1; //cin >> t;
while(t--) solve();

return 0;
}

 

TO BE CONTINUED…

难度等级(暂定)

2 比较容易

2.5 要仔细想一想

3 不会, 题解也要仔细理解