烂题三道
发表于:2023-03-28 | 分类: 随题Coding
字数统计: 1.8k | 阅读时长: 9分钟 | 阅读量:

算是最近做的几道题里觉得值得回顾一下的吧

ABC295_D
题意:给定一个由0~9组成的字符串S, 且S<=5e5|S| <= 5e5,求存在多少个l,rl,r满足Sl,...SrS_l,...S_r中满足每个数字出现偶数次个

思路:假设我们只考虑 llrr 中的一种数字,如果我们求该数字的个数前缀和,如果满足题意的话那么 $sum[r] - sum[l - 1] = 0 (mod 0) $,那么显然这俩个的奇偶性是相同的。
那么我们可以用map来从前往后记录每种出现的状态的个数,用pre[i][j]来记录数字i到j位置出现的个数。
长度为10的状态st表示每一个数字出现个数的奇偶性

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
#include <bits/stdc++.h>
#define rep(i, n, m) for(int i = n; i <= m; ++i)
#define per(i, n, m) for(int i = n; i >= m; --i)
#define ll long long
using namespace std;
const int N = 5e5 + 10;
string st;
int pre[10][N];
ll ans;
map<string, int> p;
void cal(int u){
rep(i, 0, 9) st[i] = pre[i][u] % 2 + '0';
//更新当前每个状态出现个数的奇偶性
if(p.count(st))ans += 1ll * p[st];
p[st] ++;
}
int main(){
string s; cin >> s;
int n = s.size(); s = ' ' + s;
st.resize(10); //初始化状态st
rep(i, 0, 9) st[i] = '0';
p[st] ++;
rep(i, 1, n){
rep(j, 0, 9){
pre[j][i] = pre[j][i - 1] + (j == s[i] - '0');
}
cal(i);
}
cout << ans;
return 0;
}

ABC295_E
题意:给定 n,m,kn, m, k,
和一个长度为 nn 的数组,为A1,A2...AnA_1,A_2...A_n ,
其中1<=k<=n<=2000,1<=m<=2000,0<=Ai<=m1<=k<=n<=2000, 1<=m<=2000, 0<=A_i<=m
我们可以选择其中为0的位置,其值随机为 1m1到m 的一个值,然后求对该数组排序后AkA_k的期望值

思路:据我们所知期望值$E(x)=∑i*p_{x=i} =∑ p_{x>=i} ,那么对于本题,对于每个,那么对于本题,对于每个i∈(1,m),我们求排序后k位置上的,我们求排序后k位置上的A_k >= i$的概率对答案的贡献

其中,我们统计原数组中比i大的数字个数tar和等于0的个数ze,我们若要第k位大于等于i,则还需要nk+1tarn-k+1-tar 个大于等于i的数,如果我们定义此时的tar=nk+1tartar=n-k+1-tar,则当tar小于0时,无论怎么操作都能满足要求,此时对答案的贡献为1.
假设我们需要的tarze>0tar-ze>0时,说明没有足够的0来满足题目要求,所以此时对答案的贡献都是0
倘若以上情况都不是,我们的贡献为Czejpj(1p)zejC_{ze}^jp^j(1-p)^{ze-j},其中p为选中的都是大于i的情况,即p=(mi+1)/mp=(m-i+1)/m
因为这题的数值都很小,可以用地推求组合数的方法写

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
#include <bits/stdc++.h>
#define rep(i, n, m) for(int i = n; i <= m; ++i)
#define int long long
using namespace std;
const int N = 2e3 + 10, MOD = 998244353;
int a[N], C[N][N];
int qmi(int a, int b){
int ans = 1;
while(b){
if(b & 1) ans = ans * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return ans % MOD;
}
signed main(){
//求组合数
rep(i, 0, N - 1){
rep(j, 0, i){
if(j == 0) C[i][j] = 1;
else C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % MOD;
}
}
int n, m, k; cin >> n >> m >> k;
rep(i, 1, n) cin >> a[i];
int ans = 0;
rep(i, 1, m){
int tar = n - k + 1, ze = 0;
rep(j, 1, n){
if(a[j] >= i) tar --;
else if(a[j] == 0) ze ++ ;
}
if(tar < 0 || tar - ze > 0){
// 如果满条件
if(tar < 0) ans ++;
continue;
}
int p = (m - i + 1) * qmi(m, MOD - 2) % MOD;
// pp = 1 - p
int pp = ((1 - p) % MOD + MOD) % MOD;
//此时必须需要选择的 0 的个数为 tar ~ ze
rep(j, tar, ze){
int x = qmi(p, j), y = qmi(pp, ze - j);
int num = C[ze][j] % MOD * x % MOD * y % MOD;
ans = (ans + num) % MOD;
}
}
cout << ans;
}

How far away
一道LCA题,算板子吧

题意:给定T组数据,每组数据有一棵树,给定n,mn,m,分别为点数和边数,然后n1n-1行输入两个点及其边长,然后m组询问,每次查询两个结点的最近公共祖先

思路:我们利用倍增的思想,用 f[i][j]f[i][j] 记录ii结点往上(1<<j)(1 << j) 的祖先,用 $dist[i] 记录记录i到根节点的深度,同时用一个到根节点的深度,同时用一个cost[i][j]$同样用倍增的思路记录路径长度
其中我们用

1
2
3
  	for(int i  = 1; i <= n; ++ i){
lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);
}

来地推lg[i]lg[i]

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 <iostream>
#define rep(i, n, m) for(int i = n; i <= m; ++i)
#define per(i, n, m) for(int i = n; i >= m; --i)
using namespace std;
const int N = 8e5 + 10;
int w[N], h[N], ne[N], e[N], cnt, lg[N], dist[N], f[N][30], cost[N][30];
void add(int a, int b, int c){
e[cnt] = b, w[cnt] = c, ne[cnt] = h[a], h[a] = cnt++;
}
void dfs(int u, int fa){
dist[u] = dist[fa] + 1; //u的深度+1
f[u][0] = fa; //u的上一个祖先时fa
for(int i = 1; i <= lg[dist[u]]; ++ i){
//倍增更新祖先和路径长度
f[u][i] = f[f[u][i - 1]][i - 1];
cost[u][i] = cost[f[u][i - 1]][i - 1] + cost[u][i - 1];
}
for(int i = h[u]; ~i; i = ne[i]){
int j = e[i];
if(j != fa){
//更新上一层路径
cost[j][0] = w[i];
dfs(j, u);
}
}
}
int LCA(int a, int b){
int ans = 0;
//我们默认使得a的深度大于等于b的深度
if(dist[a] < dist[b]) swap(a, b);
//我们将a上升到与b同一深度的位置,同时给ans增加贡献
while(dist[a] > dist[b])
ans += cost[a][lg[dist[a] - dist[b]] - 1],
a = f[a][lg[dist[a] - dist[b]] - 1];
if(a == b) return ans;
//二者仍然不相等,将a和b同时上升到公共祖先的儿子
for(int i = lg[dist[a]] - 1; i >= 0; --i){
if(f[a][i] != f[b][i]){
ans += cost[a][i];
ans += cost[b][i];
a = f[a][i], b = f[b][i];
}
}
return ans + cost[a][0] + cost[b][0];
}
int main()
{
//ios::sync_with_stdio(0); cin.tie(0);
int T; cin >> T;
while(T--){
memset(h, -1, sizeof h);
int n, m, s; cin >> n >> m;
for(int i = 1; i <= n; ++ i){
lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);
}
rep(i, 1, n - 1){
int a, b, c; cin >> a >> b >> c;
add(a, b, c); add(b, a, c);
}
dfs(1, 0); //我们默认以1为根节点深搜
while(m --){
int a, b; cin >> a >> b;
cout << LCA(a, b) << '\n';
}
}
return 0;
}
上一篇:
割点
下一篇:
字典树板子