4.21 模拟赛

A 未来

注意树是按照 prufer 序随机生成的,因此树高期望为 $\sqrt n$ ,我们考虑对于每个点,计算它的所有祖先对它的贡献。

比如设当前考虑的点为 $u$ ,其祖先为 $v_1$ ,考虑设祖先到这个点的路径是 $v_1,v_2\dots v_k,u$ ,我们知道 $u$ 的贡献只和这个路径的点在排列中的相对顺序有关。那么怎么计算 $v_1$ 对 $u$ 的贡献?

我们不妨暂时将这条链重新编号,看成 $1,2,\dots k$ ,我们要统计所有排列下 $1$ 对最后的 $u$ 点的贡献。发现这个贡献其实就是所有排列的以 $1$ 开头的上升子序列个数和!考虑某种排列的某条上升子序列 $p_1,p_2,\dots p_t$,我们考虑这样一种贡献 $p_1 \to p_2 \to\dots \to p_t \to u$ ,这样就会有一种方案对 $u$ 造成了贡献。每种上升子序列对应唯一一种造成贡献的方法。

这个个数很好求,考虑枚举有多少个在 $1$ 后面的数,然后相当于我们已经钦定了这 $j + 1$ 个数字的位置,剩下的数任意排列的方案。

这个可以直接暴力预处理。因为树高度期望下是 $O(\sqrt n)$ 的。

注意最终需要加上自己本身最后的贡献以及最后自己对自己的贡献(未被计算),也就是 $2\times n!\times \sum v_i$

最终复杂度 $O(n\sqrt n)$ 。

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
#include "iostream"
#include "algorithm"
#include "cstring"
#include "cstdio"
#include "cmath"
#include "vector"
#include "map"
#include "set"
#include "queue"
using namespace std;
#define MAXN 200006
//#define int long long
#define rep(i, a, b) for (int i = (a), i##end = (b); i <= i##end; ++i)
#define per(i, a, b) for (int i = (a), i##end = (b); i >= i##end; --i)
#define pii pair<int,int>
#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define eb emplace_back
#define vi vector<int>
#define all(x) (x).begin() , (x).end()
#define mem( a ) memset( a , 0 , sizeof a )
typedef long long ll;
const int P = 998244353;
int n , m;
int A[MAXN];
vi G[MAXN];
int J[MAXN] , iJ[MAXN] , T[MAXN];
vi pre;
int res = 0 , siz[MAXN];
int iv2;
int Pow(int x,int a) {
int ret = 1;
while( a ) {
if( a & 1 ) ret = 1ll * ret * x % P;
x = 1ll * x * x % P , a >>= 1;
}
return ret;
}
inline int C( int a , int b ) {
return J[a] * 1ll * iJ[b] % P * iJ[a - b] % P;
}
int c[MAXN];
void dfs( int u , int fa ) {
int re = 0;
for( int i = 1 ; i <= pre.size() ; ++ i ) {
int t = pre[pre.size() - i];
res += t * 1ll * ( T[i + 1] ) % P * J[n - i - 1] % P * c[i + 1] % P , res %= P;
}
pre.pb( A[u] );
for( int v : G[u] ) if( v != fa )
dfs( v , u );
pre.pop_back();
}
void solve() {
iv2 = ( P + 1 ) / 2;
cin >> n;
J[0] = iJ[0] = 1; rep( i , 1 , n ) J[i] = 1ll * J[i - 1] * i % P , iJ[i] = Pow( J[i] , P - 2 );
rep( i , 0 , n ) c[i] = C( n , i );
rep( i , 1 , 3000 ) rep( j , 0 , i - 1 ) T[i] += C( i - 1 , j ) * 1ll * J[i] % P * iJ[1 + j] % P , T[i] %= P;
int u , v;
rep( i , 2 , n ) scanf("%d%d",&u,&v) , G[u].pb( v ) , G[v].pb( u );
rep( i , 1 , n ) scanf("%d",A + i) , ( res += A[i] ) %= P;
res = 2ll * res * J[n] % P;
dfs( 1 , 1 );
cout << res << endl;
}

signed main() {

// int T;cin >> T;while( T-- ) solve();
solve();
}


B 幸运

我们先考虑无修改的情况。

假设到达每个点的概率是 $p_i$ ,每个点到根的距离是 $l_i$ 。那么走到 $k$ 的期望次数就是 $\frac 1 {p_k}$ 。也就是说我们有 $\frac 1 {p_k} - 1$ 次是不经过 $k$ 走到某个叶子。那么期望步数是 $\frac{\sum_{i\in leaf,i\notin k} ( l_ip_i )}{\sum_{i\in leaf,i\notin k } p_i}$ 。

为了方便,不妨设 $p = p_k$ ,于是这个步数就是

所以所需要的时间是

于是可以过 50 pts。


我们考虑可以离线询问,然后整个树的形态就有了。首先我会计算 $l_i$ ,维护深度即可。

然后我们要维护的是 $p$ 以及 $\sum_{u \in leaf,i\notin k} l_ip_i$。

维护子树信息,放到 dfs 序上就变成了区间操作。

我们考虑打一种 “不是叶子” 标记,打了这个标记后子树内的 $p_i,p_il_i$ 都不参与计算。

维护 $p$ ?我们可以直接维护到达每个叶子的概率 $p_i$ 。考虑删除一个子树的操作。假设删除 $u$ 为根的子树, $u$ 的父亲为 $f$ ,$f$ 原本儿子个数是 $s$ ,那么相当于其他子树内的点都乘上 $\frac s {s-1}$。撤销删除同理。于是 $p$ 的计算就是子树求和。

维护 $\sum l_ip_i$ ? 不难发现这和刚刚那个是同一个道理,仍然是区间乘上 $\frac s {s-1}$ 。

添加和删除的时候,需要更新父亲节点以及这个点的 “是叶子“ 标记。

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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
#include "iostream"
#include "algorithm"
#include "cstring"
#include "cstdio"
#include "cmath"
#include "vector"
#include "map"
#include "set"
#include "queue"
using namespace std;
#define MAXN 2000006
//#define int long long
#define rep(i, a, b) for (int i = (a), i##end = (b); i <= i##end; ++i)
#define per(i, a, b) for (int i = (a), i##end = (b); i >= i##end; --i)
#define pii pair<int,int>
#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define eb emplace_back
#define vi vector<int>
#define all(x) (x).begin() , (x).end()
#define mem( a ) memset( a , 0 , sizeof a )
typedef long long ll;
#define P 998244353
int n , m;
int A[MAXN];

int T[MAXN << 2] , Tl[MAXN << 2] , lz[MAXN << 2] , del[MAXN << 2];
void pu( int rt ) {
Tl[rt] = ( ( !del[rt << 1] ? Tl[rt << 1] : 0 ) + ( !del[rt << 1 | 1] ? Tl[rt << 1 | 1] : 0 ) ) % P;
}

void mul( int rt , int c ) {
lz[rt] = 1ll * lz[rt] * c % P;
Tl[rt] = 1ll * Tl[rt] * c % P;
T[rt] = 1ll * T[rt] * c % P;
}

void pd( int rt ) {
if( lz[rt] > 1 ) {
mul( rt << 1 , lz[rt] ) , mul( rt << 1 | 1 , lz[rt] );
lz[rt] = 1;
}
}

void mul( int rt , int l , int r , int L , int R , int c ) { // change p & p l
// if( rt == 1 ) printf("%d %d muled %d.\n",L,R,c);
if( L <= l && R >= r ) { mul( rt , c ); return; }
pd( rt );
int m = l + r >> 1;
if( L <= m ) mul( rt << 1 , l , m , L , R , c );
if( R > m ) mul( rt << 1 | 1 , m + 1 , r , L , R , c );
pu( rt );
}

void mdfy_p( int rt , int l , int r , int x , int c , int ll ) {
// if( rt == 1 )printf("%d change %d %d\n",x,c,ll);
if( l == r ) { T[rt] = c , Tl[rt] = c * 1ll * ll % P; return; }
pd( rt );
int m = l + r >> 1;
if( x <= m ) mdfy_p( rt << 1 , l , m , x , c , ll );
else mdfy_p( rt << 1 | 1 , m + 1 , r , x , c , ll );
pu( rt );
}

void mdfy_pl( int rt , int l , int r , int x , int ll ) {
if( l == r ) { Tl[rt] = T[rt] * 1ll * ll % P; return; }
pd( rt );
int m = l + r >> 1;
if( x <= m ) mdfy_pl( rt << 1 , l , m , x , ll );
else mdfy_pl( rt << 1 | 1 , m + 1 , r , x , ll );
pu( rt );
}

void era( int rt , int l , int r , int L , int R , int o ) {
if( L <= l && R >= r ) { del[rt] += o; return; }
pd( rt );
int m = l + r >> 1;
if( L <= m ) era( rt << 1 , l , m , L , R , o );
if( R > m ) era( rt << 1 | 1 , m + 1 , r , L , R , o );
pu( rt );
}

int getit( int rt , int l , int r , int p ) {
// if( rt == 1 ) printf("que %d\n",p);
if( l == r ) return T[rt];
pd( rt );
int m = l + r >> 1;
if( p <= m ) return getit( rt << 1 , l , m , p );
else return getit( rt << 1 | 1 , m + 1 , r , p );
}

int que( int rt , int l , int r , int L , int R ) {
if( del[rt] ) return 0;
if( L <= l && R >= r ) return Tl[rt];
pd( rt );
int m = l + r >> 1 , ans = 0;
if( L <= m ) ans = que( rt << 1 , l , m , L , R );
if( R > m ) ans += que( rt << 1 | 1 , m + 1 , r , L , R ) , ans %= P;
return ans;
}

int Pow( int x , int a ) {
int ret = 1;
while( a ) {
if( a & 1 ) ret = 1ll * ret * x % P;
x = 1ll * x * x % P , a >>= 1;
}
return ret;
}

int deg[MAXN] , cur , iv[MAXN];
vector<pii> G[MAXN];

struct ques {
int op , u;
} Q[MAXN] ;

int idx , L[MAXN] , R[MAXN] , bac[MAXN] , pp[MAXN] , sdep[MAXN] , fa[MAXN];
void dfs( int u ) {
L[u] = ++ idx , bac[idx] = u;
for( auto& t : G[u] ) {
int v = t.fi;
if( v <= n ) pp[v] = pp[u] * 1ll * iv[deg[u]] % P;
fa[v] = u;
sdep[v] = ( sdep[u] + t.se ) % P;
dfs( v );
}
R[u] = idx;
}

void build( int rt , int l , int r ) {
lz[rt] = 1;
if( l == r ) {
T[rt] = pp[bac[l]];
if( !deg[bac[l]] && bac[l] <= n )Tl[rt] = sdep[bac[l]] * 1ll * T[rt] % P;
return;
}
int m = l + r >> 1;
build( rt << 1 , l , m ) , build( rt << 1 | 1 , m + 1 , r );
pu( rt );
}

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

void solve() {
rd( n ) , rd( m );
int u , v , w;
rep( i , 2 , n ) {
rd( u ) , rd( w ) , G[u].eb( mp( i , w ) );
++ deg[u];
}
rep( i , 1 , n + m + 3 ) iv[i] = Pow( i , P - 2 );
cur = n;
rep( i , 1 , m ) {
rd( Q[i].op ) , rd( Q[i].u );
if( Q[i].op == 1 ) {
rd( w );
++ cur;
G[Q[i].u].eb( mp( cur , w ) );
Q[i].u = cur;
}
}
pp[1] = 1;
dfs( 1 );
build( 1 , 1 , cur );
int f;
rep( i , 1 , m ) {
u = Q[i].u;
if( Q[i].op == 1 ) {
// cout << Tl[1] << endl;
f = fa[u];
++ deg[f];
if( deg[f] > 1 )
mul( 1 , 1 , cur , L[f] + 1 , R[f] , ( deg[f] - 1 ) * 1ll * iv[deg[f]] % P );
else mdfy_pl( 1 , 1 , cur , L[f] , 0 );
// cout << Tl[1] << endl;
mdfy_p( 1 , 1 , cur , L[u] , getit( 1 , 1 , cur , L[f] ) * 1ll * iv[deg[f]] % P , sdep[u] );
} else if( Q[i].op == 2 ) {
f = fa[u];
-- deg[f];
if( deg[f] )
mul( 1 , 1 , cur , L[f] + 1 , R[f] , ( deg[f] + 1 ) * 1ll * iv[deg[f]] % P );
else mdfy_pl( 1 , 1 , cur , L[f] , sdep[f] );
era( 1 , 1 , cur , L[u] , R[u] , 1 );
} else if( Q[i].op == 3 ) {
u = Q[Q[i].u].u;
f = fa[u];
++ deg[f];
if( deg[f] > 1 )
mul( 1 , 1 , cur , L[f] + 1 , R[f] , ( deg[f] - 1 ) * 1ll * iv[deg[f]] % P );
else mdfy_pl( 1 , 1 , cur , L[f] , 0 );
era( 1 , 1 , cur , L[u] , R[u] , -1 );
} else {
int p = getit( 1 , 1 , cur , L[u] ) , l = ( Tl[1] - que( 1 , 1 , cur , L[u] , R[u] ) + P ) % P;
printf("%d\n",( l * 1ll * Pow( p , P - 2 ) % P + sdep[u] ) % P);
}
}
}

signed main() {
// int T;cin >> T;while( T-- ) solve();
solve();
}

C 重逢

我们考虑一种分组方案,每种方案有 $b_i$ 个。我们认为一种放石的方案是特殊的,当且仅当 $b_i \equiv -1 \pmod {2\times p_i}$ 。

如果第二个人复读第一个人的决策,那么:

对于不特殊的石子,第二个人可以模仿第一个人的决策,第一个人只能拿到 $(\lfloor \frac {b_i}{2p_i}\rfloor) val_i$ 的分数。

对于特殊的石子,第二个人可以模仿第一个人的决策,第一个人只能拿到 $(\lfloor \frac {b_i}{2p_i}\rfloor + 1) val_i$ 的分数。

于是现在的最优决策就是,拿场上权值最大的特殊石子,然后交替拿。对于第二堆也类似。

我们设第一堆的石头从大到小是 $v_1,v_2,\dots$ ,第二堆是 $w_1,w_2\dots$ ,那么第一个人能够得到的最优答案就是

于是我们可以设 $dp[i][j][0/1][0/1]$ 表示考虑到了第 $i$ 个石头,当前第一堆石头已经有 $j$ 个,当前第一堆石头在放第奇数还是偶数个特殊石子,当前第二个在放奇数还是偶数。

最后答案就是 $dp[n][\sum \frac{a_i}{2}][0/1][0/1]$ 。

复杂度是 $O((\sum a_i)^2)$。可以通过 subtask 4。


考虑如何优化这个过程?发现 subtask 5 给了个提示,一个关于 $a_i$ 的奇妙的石子很小。

发现在一堆石子中放入 $a_i,a_i+2p_i$ 得到的答案是一样的。所以我们不妨直接给 $a_i$ 模 $p_i$ 来做,这样 dp 的第二维就很小了。

这么来看,这一部分的 dp 复杂度是很优秀的,仅仅是 $O((\sum p_i)^2)$ 。

但是我们还得考虑剩下的可以自由分配的石子,这种石子是一组一组的,每组 大小是 $2p_i$ ,我们设 $r_i$ 是放在某一堆石子的数量模 $2p_i$ 的余数,于是考虑两种情况:

  • $r_i \leq a_i \pmod{2p_i}$ 这种情况下,可以自由分配 $\lfloor \frac{a_i}{2p_i} \rfloor$ 组石头。
  • $r_i > a_i \pmod{2p_i}$ 这种情况下,可以自由分配 $\lfloor \frac{a_i}{2p_i} \rfloor - 1$ 组石头。

如果这么做,分配的组数都不一样很毒瘤。于是考虑当 $r_i \le a_i$ 的时候,我们尝试分配一下 $r_i,r_i+2p_i$ ,于是自由分配的组数必然是 $\lfloor \frac{a_i}{2p_i} \rfloor - 1$ 。

这里对于自由分配的组,我们需要做个背包,看能不能凑出 $0,1,2,\dots$ 。

这样复杂度是 $O((\sum p_i)^2+(\sum a_i)(\sum \frac{a_i}{2p_i}))$ 。可以通过 subtask 5 。


这个前半段的复杂度已经很优秀了,考虑优化后半段。这个背包可以进行二进制分组,比如将 $1000$ 分成 $1,2,4,8,16,32,64,128,256,489$ 这些物品。于是可以把 $\frac{a_i}{2p_i}$ 拆成这种东西来背包,用 bitset 来优化,复杂度是 $O(\frac{n(\sum a_i)(\log \sum a_i)}{w})$ 。可以通过 subtask 6。

最后,注意到 $\sum p_i \le 2000$ 意味着 $p_i$ 的种类数量其实只有 $\sqrt{\sum p_i}$ 。于是可以合并相同的 $p_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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include "iostream"
#include "algorithm"
#include "cstring"
#include "cstdio"
#include "cmath"
#include "vector"
#include "map"
#include "set"
#include "bitset"
#include "queue"
using namespace std;
#define MAXN 2000006
//#define int long long
#define rep(i, a, b) for (int i = (a), i##end = (b); i <= i##end; ++i)
#define per(i, a, b) for (int i = (a), i##end = (b); i >= i##end; --i)
#define pii pair<int,int>
#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define eb emplace_back
#define vi vector<int>
#define all(x) (x).begin() , (x).end()
#define mem( a ) memset( a , 0 , sizeof a )
typedef long long ll;
int n , m;
struct poi {
int a , p , v;
void rd( ) {
scanf("%d%d%d",&a,&p,&v);
}
} p[MAXN] ;
bool cmp( poi a , poi b ) {
return a.v > b.v;
}
int dp[2][2][2][8050] , re[MAXN] , cur , las , t;

void Just_DOIT( int x , int i ) {
int t1 = ( x % ( 2 * p[i].p ) == 2 * p[i].p - 1 ) , t2 = ( ( p[i].a - x ) % ( 2 * p[i].p ) == 2 * p[i].p - 1 );
int v = x / ( 2 * p[i].p ) * p[i].v + ( p[i].a - x ) / ( 2 * p[i].p ) * p[i].v;
rep( u , 0 , 1 ) rep( q , 0 , 1 ) rep( j , 0 , t ) if( dp[cur ^ 1][u][q][j] >= 0 ) {
int uu = u , qq = q , vv = dp[cur ^ 1][u][q][j] + v;
if( t1 ) { uu ^= 1; if( uu ) vv += p[i].v; }
if( t2 ) { qq ^= 1; if( !qq ) vv += p[i].v; }
dp[cur][uu][qq][j + x] = max( dp[cur][uu][qq][j + x] , vv );
}
}

vi obs;
int S , sp;

void solve() {
cin >> n;
rep( i , 1 , n ) p[i].rd() , S += p[i].a , sp += p[i].p;
sort( p + 1 , p + 1 + n , cmp );
int h = 0;
cur = 0 , las = 1;
memset( dp , -0x3f , sizeof dp ); dp[0][0][0][0] = 0;
for( int i = 1 ; i <= n ; ++ i ) {
cur ^= 1 , las ^= 1;
rep( j , 0 , 1 ) rep( k , 0 , 1 ) rep( q , 0 , t ) dp[cur][j][k][q] = -0x3f3f3f3f;
h = max( p[i].a / ( 2 * p[i].p ) - 1 , 0 ) , re[p[i].p] += h;
for( int j = 0 ; j < min( 2 * p[i].p , p[i].a + 1 ) ; ++ j ) { // j : the r_i
Just_DOIT( j , i );
if( j <= p[i].a % ( 2 * p[i].p ) && j + 2 * p[i].p <= p[i].a ) Just_DOIT( j + 2 * p[i].p , i );
}
t += 4 * p[i].p;
}
bitset<500000> B;
B[0] = 1;
rep( i , 1 , MAXN - 10 ) if( re[i] ) {
int cur = 0;
obs.clear();
for( int t = 1 ; cur + t <= re[i] ; t <<= 1 )
obs.pb( t * i ) , cur += t;
if( cur != re[i] ) obs.pb( ( re[i] - cur ) * i );
for( auto& t : obs ) B |= B << t;
}
int res = 0;
rep( i , 0 , 1 ) rep( j , 0 , 1 ) for( int w = 0 ; w * 2 <= S / 2 ; ++ w ) if( B[w] && S / 2 - w * 2 <= sp * 4 )
res = max( res , dp[cur][i][j][S / 2 - w * 2] );
cout << res << endl;
}

signed main() {
// int T;cin >> T;while( T-- ) solve();
solve();
}


文章作者: yijan
文章链接: https://yijan.co/421-mo-ni-sai/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Yijan's Blog