前言

关于我想整理思路结果全忘了这件事(

复盘

B - Sample Game

期望$DP$​。设$f(x)$为$E[x]$,$g(x)$为$E[x^2]$​,最后所求的期望即为$g(0)$。

需要注意的是对$E((x+1)^2)$的处理,要使用两个数组,转化为$E(x^2)+2 * E(x) + 1$​进行递推。

这一题也有生成函数的推导方法,但是感觉没有期望$DP$容易理解。

一份比较优秀的期望$DP$推导博客:2021牛客多校#4 B-Sample Game(概率DP)

image-20210828005935702

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
/* vegetable1024 | liushan.site | Maybe Lovely? */

#include <bits/stdc++.h>
#define maxn 800005
#define int long long
using namespace std;

//DEBUG TEMPLATE
template<typename T>
void Print(T value){
std::cout << value << '\n';
}
template<typename Head, typename... Rail>
void Print(Head head, Rail... rail){
std::cout << head << ", ";
Print(rail...);
}

int read(){
int x = 0; char ch = getchar();
while(ch < '0' || ch > '9') ch = getchar();
while(ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
return x;
}

int qpow(int a, int b, int p){
if(b == 0) return 1;
int tmp = qpow(a, b / 2, p);
tmp = (tmp * tmp) % p;
if(b & 1) tmp = (tmp * a) % p;
return tmp;
}

const int p = 998244353;
int inv(int val){
if(val < 0) val = (val % p) + p;
return qpow(val, p - 2, p);
}

int n, sw, w[maxn], pi[maxn], f[maxn], g[maxn];


signed main(void)
{
n = read();
for(int i = 1; i <= n; i++) w[i] = read(), sw = (sw + w[i]) % p;
for(int i = 1; i <= n; i++) pi[i] = (w[i] * inv(sw)) % p;
for(int i = n; i >= 0; i--)
{
int sum = 0;
for(int j = i + 1; j <= n; j++)
sum = (sum + ((pi[j] * f[j]) % p)) % p;
f[i] = ((1 + sum) * inv(1 - pi[i])) % p;
}
for(int i = n; i >= 0; i--)
{
int sum = 0;
for(int j = i + 1; j <= n; j++)
sum = ((sum + ((pi[j] * g[j]) % p) % p) + ((2 * pi[j] * f[j]) % p)) % p;
g[i] = (((((1 + 2 * f[i] * pi[i]) % p) + sum) % p) * inv(1 - pi[i])) % p;
}
printf("%lld\n", g[0]);
return 0;
}

C - LCS

构造题,一般来说优先考虑约束最弱的条件,再依次推导,尝试构造一个长度最小的解。

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
/* vegetable1024 | liushan.site | Maybe Lovely? */

#include <bits/stdc++.h>
#define maxn 800005
#define int long long
using namespace std;
int a, b, c, n;
string s1, s2, s3;
int read(){
int x = 0; char ch = getchar();
while(ch < '0' || ch > '9') ch = getchar();
while(ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
return x;
}

signed main(void)
{
int f1 = 0, f2 = 0, f3 = 0;
a = read(); b = read(); c = read(); n = read();

int ra = a, rb = b, rc = c;
if(ra > rb) swap(ra, rb);
if(ra > rc) swap(ra, rc);
if(rb > rc) swap(rb, rc);

for(int i = 1; i <= ra; i++)
s1 += 'a', s2 += 'a', s3 += 'a';
for(int i = 1; i <= a - ra; i++)
s1 += 'g', s2 += 'g';
for(int i = 1; i <= b - ra; i++)
s2 += 'b', s3 += 'b';
for(int i = 1; i <= c - ra; i++)
s1 += 'c', s3 += 'c';

while(s1.length() < n) s1 += 'd';
while(s2.length() < n) s2 += 'e';
while(s3.length() < n) s3 += 'f';

if(s1.length() > n || s2.length() > n || s3.length() > n) cout << "NO\n";
else
{
cout << s1 << '\n';
cout << s2 << '\n';
cout << s3 << '\n';
}
return 0;
}

E - Tree Xor

狗题。出题人利用了一个很巧妙的二进制位的性质,使得这一题可以区间维护。

首先把问题简化,令任意一个$w[i] = 0$​,由此可以唯一推导出树上其他节点的值$w’[i]$。

而后,令最初的$w[i]=a$,可以发现,树上其他节点的值均会$xor \ a$,也就是说,我们需要解决:

存在多少个$a$,使得$a \ xor \ w’[i] \in [l_i,r_i]\ $。

可以转化为,将每一$w’[i] \ xor \ [l_i, r_i]$解出,得到若干个区间,最后对这些区间求交,则得到所有可取的$a$。

本题的难点就在这里,普通情况下$[l_i, r_i] \ xor \ C$得到的是不连续的区间,难以进行统计。

这里构造一棵线段树,最开始的时候,维护的左右端点为$[(000 \dots 00)_2, (111 \dots 11)_2]$。

这是为什么呢?以一个较小的例子引入:

$[0/000, 7/111] \rightarrow [0/000, 3/011], [4/100, 7/111] \rightarrow [0/000, 1/001],[2/010, 3/011],[4/100,5/101],[6/110, 7/111]$

可以看出,前$x$位二进制相同,而后$y$位均为$(000 \dots 00)_2$和$(111 \dots 11)_2$。

而任何数$C$在异或后$y$位为$(000 \dots 00)_2 \rightarrow (111 \dots 11)_2$的区间后

得到的数字仍在$(000 \dots 00)_2 \rightarrow (111 \dots 11)_2$​​内,由此保证了区间的连续。

因此,只需要对前$x$位异或。最后得到的区间就是:$(@@@\dots@@0000)_2, (@@@\dots@@0000)_2 + len$

其中,$@@@$为前$x$位异或后得到的值,而$len$​是原来区间长度。

最后求一个区间交即可。

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
/* vegetable1024 | liushan.site | Maybe Lovely? */

#include <bits/stdc++.h>
#define maxn 800005
#define int long long
using namespace std;
int n, ll[maxn], rr[maxn], wn[maxn];
int read(){
int x = 0; char ch = getchar();
while(ch < '0' || ch > '9') ch = getchar();
while(ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
return x;
}
int k, head[maxn];
struct Edge{
int to;
int nxt;
int weight;
}E[maxn];
void add(int u, int v, int w){
E[k].to = v;
E[k].nxt = head[u];
E[k].weight = w;
head[u] = k++;
}
map<int, int> mp;
void calc(int lf, int rt, int wi){
int len = rt - lf + 1;
int nlf = (lf ^ wi) & (~(len - 1));
int nrt = nlf + len;
mp[nlf]++; mp[nrt]--;
}
void modify(int lf, int rt, int wl, int wr, int val){
if(lf > wr || rt < wl){
return;
}
if(wl <= lf && rt <= wr){
calc(lf, rt, val); return;
}
int mid = (lf + rt) / 2;
modify(lf, mid, wl, wr, val);
modify(mid + 1, rt, wl, wr, val);
}
void dfs(int now, int fa, int val){
wn[now] = val;
for(int i = head[now]; ~i; i = E[i].nxt)
{
if(E[i].to == fa) continue;
dfs(E[i].to, now, val ^ E[i].weight);
}
}
signed main(void)
{
memset(head, -1, sizeof(head));
n = read();
for(int i = 1; i <= n; i++)
ll[i] = read(), rr[i] = read();
for(int i = 1; i <= n - 1; i++)
{
int u = read(), v = read(), w = read();
add(u, v, w);
add(v, u, w);
}
dfs(1, 0, 0);
for(int i = 1; i <= n; i++)
modify(0, (1 << 30) - 1, ll[i], rr[i], wn[i]);
int s = 0, las = 0, ans = 0;
for(auto x : mp){
if(s >= n) ans += x.first - las;
s += x.second;
las = x.first;
}
printf("%lld\n", ans);
return 0;
}

F - Just a joke

签到题。之前读的时候差点读假了QwQ

image-20210828012534454

没有环的连通分量,实际上就是一棵树。

因此,若选择操作一,删除了一条边之后,会剩下一个孤立的点,或两个连通分量

如果删掉一条边之后剩下一个孤立的点,那么这个删除对获胜没有半点帮助。

如果是两个连通分量,则需要进一步推导。最终可以推出胜负只和连通分量的数目与环的数目有关。

对两个数目的和求一下奇偶性就能做出来了。

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
/* vegetable1024 | oi-liu.com | Maybe Lovely? */

#include <bits/stdc++.h>
#define maxn 100005
using namespace std;
int n, m, cnt;
int uset[maxn];
int find(int x){
return x == uset[x] ? x : uset[x] = find(uset[x]);
}
void unioon(int x, int y){
int fx = find(x);
int fy = find(y);
if(fx == fy)
{
cnt++;
return;
}
uset[fx] = fy;
}
int main(void)
{
scanf("%d %d", &n, &m);
for(int i = 1; i <= n; i++) uset[i] = i;
for(int i = 1; i <= m; i++)
{
int u, v;
scanf("%d %d", &u, &v);
unioon(u, v);
}
for(int i = 1; i <= n; i++)
if(find(i) == i) cnt++;
printf(cnt & 1 ? "Alice\n" : "Bob\n");
return 0;
}

事实上还能更加简单,不需要并查集维护。

image-20210828013339400

Just a Joker

I - Inverse Pair

签到题。当且仅当$x$在$x+1$后面的时候,构造$b[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
/* vegetable1024 | oi-liu.com | Maybe Lovely? */

#include <bits/stdc++.h>
#define maxn 800005
#define int long long
using namespace std;
int n, ans;
int a[maxn], o[maxn], tmp[maxn];
void merge(int lf, int rt){
if(lf == rt) return;
int mid = (lf + rt) / 2;
merge(lf, mid);
merge(mid + 1, rt);
int i = lf, j = mid + 1, k = lf;
while(i <= mid && j <= rt)
{
if(o[i] > o[j])
tmp[k++] = o[j++], ans += mid - i + 1;
else
tmp[k++] = o[i++];
}
while(i <= mid) tmp[k++] = o[i++];
while(j <= rt) tmp[k++] = o[j++];
for(int i = lf; i <= rt; i++) o[i] = tmp[i];
}
set<int> s1;
int vis[maxn];
signed main(void)
{
scanf("%lld", &n);
for(int i = 1; i <= n; i++)
scanf("%lld", &a[i]), o[i] = a[i];
merge(1, n);
int flag = 0;
for(int i = 1; i <= n; i++) s1.insert(a[i]);
for(int i = 1; i <= n; i++)
{
s1.erase(a[i]);
if(!vis[a[i]] && !vis[a[i] - 1] && s1.count(a[i] - 1))
{
vis[a[i] - 1] = 1;
flag++;
//printf("debug: %lld\n", a[i] - 1);
//break;
}
vis[a[i]] = 1;
}
printf("%lld\n", ans - flag);
return 0;
}

J - Average

前置知识:二分求最大平均值的写法(虽然是板子,但是比赛时候写挂了…)

不知道为什么比赛脑抽写了尺取,喜提3.23

主要是将序列平均值转化为利用前缀和$O(n)$求的非负序列问题,再进行$check$​就可以了。

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
/* vegetable1024 | oi-liu.com | Maybe Lovely? */

#include <bits/stdc++.h>
#define maxn 800005
#define int long long
using namespace std;

int read(){
int x = 0; char ch = getchar();
while(ch < '0' || ch > '9') ch = getchar();
while(ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
return x;
}
int n, m, x, y;
int a[maxn], b[maxn];
double s[maxn];
bool check(int num[], int len, int lim, double avg){
for(int i = 1; i <= len; i++)
s[i] = s[i - 1] + num[i] - avg;
double minn = 0;
for(int i = lim; i <= len; i++)
{
if(s[i] >= minn) return true;
minn = min(minn, s[i - lim + 1]);
}
return false;
}
double solve(int num[], int len, int lim){
double lf = 0, rt = 1e5;
while(fabs(rt - lf) > 1e-8)
{
double mid = (lf + rt) / 2;
check(num, len, lim, mid) ? lf = mid : rt = mid;
}
return lf;
}
signed main(void)
{
n = read(); m = read(); x = read(); y = read();
for(int i = 1; i <= n; i++) a[i] = read();
for(int i = 1; i <= m; i++) b[i] = read();
double ans1 = solve(a, n, x);
double ans2 = solve(b, m, y);
printf("%.10lf", ans1 + ans2);
return 0;
}