技巧一: 势能函数
有一类题目,需要计算期望操作次数, 传奇OIer djq 给出了通用解法, 利用势能函数得出递推方程, 再根据 初末状态的势能差得出答案.
例题1: 951F. Company Acquisitions
有 n n n 个点, 它们会形成若干个高度 ≤ 2 \le 2 ≤2 的有根树.
操作为: 随机选择两个根 A , B A,B A,B. 然后以相同概率 (1/2) 支配对方.
若 A A A 支配 B B B, 则 B B B 变为 A A A 的儿子, 而原本 B B B 的儿子们变成独立的(单点)有根树. 如下图:
分析: 设 f ( x ) f(x) f(x) 为一棵 第二层有 x x x 个节点的有根树的势能函数.
设此时有
k
k
k 棵树, 第二层节点数分别为
x
1
,
x
2
,
…
,
x
k
x_1,x_2, \dots, x_k
x1,x2,…,xk.
∑
i
=
1
k
f
(
x
i
)
=
1
(
1
次操作
)
+
∑
i
(
k
−
2
k
f
(
x
i
)
+
∑
j
≠
i
1
2
k
∗
(
k
−
1
)
(
f
(
x
i
+
1
)
+
x
j
∗
f
(
0
)
+
f
(
x
j
+
1
)
+
x
i
∗
f
(
0
)
)
)
∑
2
k
f
(
x
i
)
=
1
+
∑
i
1
k
∗
(
x
i
f
(
0
)
+
f
(
x
i
+
1
)
)
∑
f
(
x
i
)
=
∑
i
(
1
+
x
i
f
(
0
)
+
f
(
x
i
+
1
)
)
/
2
f
(
x
+
1
)
=
2
f
(
x
)
−
1
−
x
f
(
0
)
what’s f(0)? For convenience, let f(0) = 0.(势能可以随便设置)
\sum_{i=1}^k f(x_i) = 1(1次操作) +\sum_i (\dfrac{k - 2} k f(x_i) + \sum_{j \ne i} \dfrac 1{2k * (k - 1)} (f(x_i + 1) + x_j*f(0) + f(x_j + 1) + x_i*f(0))) \\ \sum \dfrac{2} k f(x_i) =1 + \sum_i \dfrac{1} k* (x_if(0)+f(x_i+1)) \\ \sum f(x_i) = \sum_i (1+x_if(0) + f(x_i+1))/2 \\ f(x+1) = 2f(x)-1-xf(0) \\ \text{what's ~ f(0)? ~~For convenience, let f(0) = 0.(势能可以随便设置)}
i=1∑kf(xi)=1(1次操作)+i∑(kk−2f(xi)+j=i∑2k∗(k−1)1(f(xi+1)+xj∗f(0)+f(xj+1)+xi∗f(0)))∑k2f(xi)=1+i∑k1∗(xif(0)+f(xi+1))∑f(xi)=i∑(1+xif(0)+f(xi+1))/2f(x+1)=2f(x)−1−xf(0)what’s f(0)? For convenience, let f(0) = 0.(势能可以随便设置)
直接递推即可. 最后的目标状态势能是 f ( n − 1 ) f(n-1) f(n−1).
int n, m, d[N];
ll f[N];
void solve() {
qr(n);
int x;
FOR(i, n) {
qr(x);
if(~x) d[x]++, d[i] = -1; // degree
}
f[0] = 0;
rep(i, 0, n) {
f[i + 1] = (2 * f[i] - i * f[0] - 1) % mod;
dl(f[i + 1], 0);
}
int ans = 0;
FOR(i, n) if(~d[i]) ad(ans, f[d[i]]); // 初始状态势能
dl(ans, f[n - 1]); // 末状态势能
pr2(ans);
}
例题2: 1349D. Slime and Biscuits
有 n n n 个人玩传饼干游戏, 一开始第 i i i 个人有 a i a_i ai 块.
每轮在 ∑ i = 1 n a i \sum_{i=1}^n a_i ∑i=1nai 块中均匀随机出一块, 让其主人再 等概率发给其他 n − 1 n-1 n−1 个人之一.
如果一个人拥有了全部的饼干, 游戏结束.
n ≤ 1 0 5 , m = ∑ i = 1 n a i ≤ 3 ∗ 1 0 5 n \le 10^5, m=\sum_{i=1}^n a_i \le 3*10^5 n≤105,m=∑i=1nai≤3∗105.
设 f ( x ) f(x) f(x) 表示一个人拥有了 x x x 块饼干的势能.
则有
f
(
a
)
=
∑
i
=
1
n
f
(
a
i
)
n
=
1
n
∑
i
=
1
n
1
+
a
i
m
(
f
(
a
i
−
1
)
+
∑
j
≠
i
(
n
−
2
n
−
1
f
(
a
j
)
+
1
n
−
1
f
(
a
j
+
1
)
)
)
∑
i
=
1
n
f
(
a
i
)
=
∑
i
=
1
n
(
a
i
m
(
f
(
a
i
−
1
)
+
m
a
i
)
+
(
m
−
a
i
)
(
n
−
2
)
m
(
n
−
1
)
f
(
a
i
)
+
m
−
a
i
m
(
n
−
1
)
f
(
a
i
+
1
)
)
)
)
l
e
t
x
=
a
i
,
f
(
x
)
−
f
(
x
+
1
)
=
(
n
−
1
)
m
−
x
(
x
(
f
(
x
−
1
)
−
f
(
x
)
)
−
m
)
f(a) = \dfrac{\sum_{i=1}^n f(a_i)}n = \dfrac 1 n\sum_{i=1}^n 1 +\dfrac {a_i}m (f(a_i-1) + \sum_{j\ne i} (\dfrac {n-2}{n-1} f(a_j) + \dfrac {1}{n-1}f(a_j+1))) \\ \sum_{i=1}^n f(a_i) = \sum_{i=1}^n (\dfrac {a_i}m (f(a_i-1)+\dfrac m{a_i}) + \dfrac {(m-a_i)(n-2)}{m(n-1)} f(a_i) + \dfrac {m-a_i}{m(n-1)}f(a_i+1))) )\\ let ~ x=a_i, f(x)-f(x+1) = \dfrac{(n-1)}{m-x} (x(f(x-1) - f(x))-m)
f(a)=n∑i=1nf(ai)=n1i=1∑n1+mai(f(ai−1)+j=i∑(n−1n−2f(aj)+n−11f(aj+1)))i=1∑nf(ai)=i=1∑n(mai(f(ai−1)+aim)+m(n−1)(m−ai)(n−2)f(ai)+m(n−1)m−aif(ai+1))))let x=ai,f(x)−f(x+1)=m−x(n−1)(x(f(x−1)−f(x))−m)
边界:
f
(
m
)
=
0
,
f
(
0
)
−
f
(
1
)
=
n
−
1
f(m)=0, f(0)-f(1)=n-1
f(m)=0,f(0)−f(1)=n−1.
原因: 结束位置势能最低; 一个0饼干的位置得到一个饼干的概率为 1 n − 1 \dfrac 1 {n-1} n−11, 故期望要 n − 1 n-1 n−1 步才能得到一块.
Code:
const int N = 3e5 + 10;
ll n, m, a[N], f[N];
void solve() {
qr(n); m = 0;
FOR(i, n) qr(a[i]), m += a[i];
// let f(m) = 0. m是势能最低点
f[0] = n - 1; // f(0) - f(1)
FOR(i, m - 1) f[i] = (n - 1) % mod * power(m - i, mod - 2, mod) % mod * (f[i - 1] * i % mod + m) % mod;
REP(i, 0, m - 1) ad(f[i], f[i + 1]);
ll ans = 0;
FOR(i, n) ad(ans, f[a[i]]);
// dl(ans, f[m]); // f(m) = 0
dl(ans, (n - 1) * f[0] % mod);
pr2(ans * power(n, mod - 2, mod) % mod);
}
例题3: 1951G - Clacking Balls
有围成环形的 m m m 个篮子, n n n 个球( m ≥ n m\ge n m≥n).
操作:
- 每次随机一个 i ∈ [ 1 , n ] i\in [1,n] i∈[1,n], 如果球 i i i 不存在,则无变化.
- 否则, 令球 i i i 移动到下一个篮子, 若该篮子有球,则丢弃球 i i i.
结束状态: 只剩一个球.
n ≤ 3 ∗ 1 0 5 , m ≤ 1 0 9 n\le 3*10^5,m\le 10^9 n≤3∗105,m≤109.
设当前剩余 k k k 个球, 每个球距离前一个球的距离为 a i a_i ai,
f ( A ) = f ( a 1 , a 2 , . . . , a k ) = 1 + n − k n f ( A ) + 1 n ∑ i = 1 k f ( a 1 , . . . , a i + 1 , a i + 1 − 1 , a i + 2 , . . . ) f(A)=f(a_1,a_2,...,a_k)=1+\dfrac {n-k} n f(A)+\dfrac 1 n\sum_{i=1}^k f(a_1,...,a_i+1, a_{i+1}-1,a_{i+2},...) f(A)=f(a1,a2,...,ak)=1+nn−kf(A)+n1∑i=1kf(a1,...,ai+1,ai+1−1,ai+2,...).
为了方便,记 f ( A + { i } ) = f ( a 1 , . . . , a i + 1 , a i + 1 − 1 , a i + 2 , . . . ) f(A+\{i\}) = f(a_1,...,a_i+1, a_{i+1}-1,a_{i+2},...) f(A+{i})=f(a1,...,ai+1,ai+1−1,ai+2,...).
则
k
f
(
A
)
=
n
+
∑
i
=
1
k
f
(
A
+
{
i
}
)
n
=
∑
i
=
1
k
f
(
A
)
−
f
(
A
+
{
i
}
)
=
d
e
f
∑
i
=
1
k
h
(
A
,
i
)
kf(A)=n+\sum_{i=1}^k f(A+\{i\}) \\ n=\sum_{i=1}^k f(A)-f(A+\{i\}) {\overset{def} {=}}\sum_{i=1}^k h(A,i)
kf(A)=n+i=1∑kf(A+{i})n=i=1∑kf(A)−f(A+{i})=defi=1∑kh(A,i)
令 f ( A ) = ∑ i = 1 k ∑ i = 0 a i g ( i ) f(A)=\sum_{i=1}^k \sum_{i=0}^{a_i} g(i) f(A)=∑i=1k∑i=0aig(i)(解构序列为元素的叠加, 抛弃顺序信息), 则 n = ∑ i = 1 k g ( a i ) − g ( a i + 1 ) n=\sum_{i=1}^k g(a_i)-g(a_i+1) n=∑i=1kg(ai)−g(ai+1).
为了让 部分 a i = 0 , k = n a_i=0,k=n ai=0,k=n 时上式恒成立, 我们令 g ( 0 ) = g ( 1 ) = 0 g(0)=g(1)=0 g(0)=g(1)=0.
注意到 ∑ a i = m \sum a_i= m ∑ai=m, 故可令
g ( x + 1 ) − g ( x ) = − n m x → g ( x ) = − n m ( x 2 ) → s g ( a ) = ∑ i = 0 a g ( i ) = − n m ( a + 1 3 ) g(x+1)-g(x) = -\dfrac n m x \rightarrow g(x) = -\dfrac n m \dbinom x 2 \rightarrow sg(a)=\sum_{i=0}^a g(i)=-\dfrac n m \dbinom {a+1} 3 g(x+1)−g(x)=−mnx→g(x)=−mn(2x)→sg(a)=∑i=0ag(i)=−mn(3a+1)
故势能函数为 f ( A ) = ∑ i = 1 n s g ( a i ) f(A)=\sum_{i=1}^n sg(a_i) f(A)=∑i=1nsg(ai). 答案为 f ( A ) − f ( ( m ) ) = n m ( ( m + 1 3 ) − ∑ i = 1 n ( a i + 1 3 ) ) f(A)-f((m))=\dfrac n m (\binom {m+1}3- \sum_{i=1}^n \binom {a_i+1}3 ) f(A)−f((m))=mn((3m+1)−∑i=1n(3ai+1))
const int N = 3e5 + 10, inv6 = power(6, mod - 2, mod);
int n, m, a[N];
ll f(ll x) {
x++; return x * (x - 1) % mod * (x - 2) % mod * inv6 % mod;
}
void solve() {
qr(n, m);
FOR(i, n) qr(a[i]);
sort(a + 1, a + n + 1);
a[0] = a[n] - m;
ll ans = f(m);
FOR(i, n) dl(ans, f(a[i] - a[i - 1]));
pr2(ans * n % mod * power(m, mod - 2, mod) % mod);
}
习题:
总结:
本做法需要有极强的 找通解 能力, 需要一定的观察力和解耦函数能力.
技巧二: 随机排列
这个技巧的实现方式是:
-
初始化最优解 集合(序列) S ∗ = ∅ S^*=\empty S∗=∅, 设置循环次数 T T T.(一般要充分卡时,设置得尽量大)
-
随机一个排列 P P P, 维护一个合法集合 S = ∅ S=\empty S=∅,顺序遍历 P P P, 看 P i P_i Pi 是否能加入 S S S, 如果加入合法则加入( S ← S ∪ { P i } S\leftarrow S \cup \{P_i\} S←S∪{Pi}).
-
判断 S S S 是否比 S ∗ S^* S∗ 更优, 若是, 则更新 S ∗ = S S^*=S S∗=S.
- 若
--T>0
, 重复操作2. - 否则, 结束求解.(认为找到了最优解)
- 若
如果 ∣ P ∣ |P| ∣P∣ 比较小, 那么可以直接全排列. 否则 T = TL(s) ∗ 1 0 8 / F ( n ) T=\text{TL(s)} * 10^8 /F(n) T=TL(s)∗108/F(n), F ( n ) F(n) F(n) 为计算一次答案的复杂度.
技巧三: 纳什均衡
纳什均衡是非合作 、信息透明(知道其他参与方决策分布)的博弈.
对于每个参与方满足 u i ( s i ∗ , s − i ∗ ) ≥ u i ( s i , s − i ∗ ) f o r a l l s i ∈ S i {\displaystyle u_{i}(s_{i}^{*},s_{-i}^{*})\geq u_{i}(s_{i},s_{-i}^{*})\;\;{\rm {for\;all}}\;\;s_{i}\in S_{i}} ui(si∗,s−i∗)≥ui(si,s−i∗)forallsi∈Si, 其中 s ∗ = ( s i ∗ , s − i ∗ ) {\displaystyle s^{*}=(s_{i}^{*},s_{-i}^{*})} s∗=(si∗,s−i∗) 是一个纳什均衡点, s i ∈ S i s_i \in S_i si∈Si 表示第 i i i 个人的决策, u i u_i ui 表示第 i i i 个人的收益.
严格纳什均衡: u i ( s i ∗ , s − i ∗ ) > u i ( s i , s − i ∗ ) f o r a l l s i ∈ S i , s i ≠ s i ∗ {\displaystyle u_{i}(s_{i}^{*},s_{-i}^{*})>u_{i}(s_{i},s_{-i}^{*})\;\;{\rm {for\;all}}\;\;s_{i}\in S_{i},s_{i}\neq s_{i}^{*}} ui(si∗,s−i∗)>ui(si,s−i∗)forallsi∈Si,si=si∗, 保证了纳什均衡点的唯一.
总结: 纳什均衡是只谋私利, 不求共赢的.(囚徒困境…)
例题1: 1912F - Fugitive Frenzy
警察在一棵树上追捕逃犯,在 0 0 0 时刻,警察出现在编号为 s s s 的顶节点,而逃犯则会选择一个节点出现,之后从警察开始,他们会轮流行动。
- 每次轮到警察移动时,他可以任意选择一个与当前节点相邻的节点并移动过去。警察移动的时间为一个单位时间。此外,警察也可以决定静止不动,在这种情况下,他会在开始移动的顶点等待一个单位时间。如果在移动结束时,警察与逃犯出现在同一个节点,他会立即抓住逃犯,追捕结束。
- 每次轮到逃犯移动时,假设他位于点 b b b ,而警察位于点 p p p 。逃犯会选择一个 p p p 不在 b b b 到 b ′ b' b′ 的路径上的点 b ′ b' b′ 并立即移动到 b ′ b' b′,他的移动不需要花费时间,且他也可以选择停留在原地。
逃犯每时每刻都知道警官的位置(包括 s s s )。相反,警官对逃犯的行踪一无所知,只有在抓住逃犯的那一刻才能发现他。
警察的目标是尽快抓住逃犯,而逃犯的目标是尽可能晚地被抓住,在警察和逃犯都采取最优行动的情况下,求追捕时间的期望。
2 ≤ n ≤ 100 2 \le n \le 100 2≤n≤100
首先, 逃犯一定会跑到叶子, 警察也只会往叶子跑. 当警察到达叶子后, 逃犯重新选择叶子节点.
现在求双方的纳什均衡点. 设 p u , v , q u , v p_{u,v},q_{u,v} pu,v,qu,v 分别表示 s = u s=u s=u 时到警察到 v v v的概率, 逃犯到 v v v 的概率,
x u x_u xu 为警察在 u u u 时的期望追捕时间, 叶子集合为 L L L.
显然有 ∀ u ≠ v , v ∈ L , p u , v , q u , v > 0 \forall u\ne v,v\in L, p_{u,v},q_{u,v}>0 ∀u=v,v∈L,pu,v,qu,v>0.
x
u
x_u
xu 的转移方程, 双方梯度:
x
u
=
∑
v
∈
L
\
{
u
}
F
(
u
,
v
)
w
h
e
r
e
F
(
u
,
v
)
=
p
u
,
v
(
d
i
s
t
(
u
,
v
)
+
(
1
−
q
u
,
v
)
x
v
)
.
F
o
r
p
o
l
i
c
e
:
P
u
(
v
)
=
∂
∂
p
u
.
v
F
(
u
,
v
)
=
(
d
i
s
t
(
u
,
v
)
+
(
1
−
q
u
.
v
)
x
v
)
F
o
r
f
u
g
i
t
i
v
e
:
Q
u
(
v
)
=
∂
∂
q
u
,
v
F
(
u
,
v
)
=
−
p
u
,
v
x
v
x_{u}=\sum_{v\in{\cal L}\backslash\{u\}}F(u,v)\qquad{\mathrm{where~}}F(u,v)=p_{u,v}({\mathrm{dist}}\,(u,v)+(1-q_{u,v})x_{v}). \\ For~ police: P_{u}(v)\:=\:\frac{\partial}{\partial p_{u.v}}F(u,v)\:=\:(\mathrm{dist}\:(u,v)+(1-q_{u.v})x_{v}) \\ For~fugitive: Q_u(v) = \frac ∂{∂q_{u,v}} F(u,v) = −p_{u,v}x_v
xu=v∈L\{u}∑F(u,v)where F(u,v)=pu,v(dist(u,v)+(1−qu,v)xv).For police:Pu(v)=∂pu.v∂F(u,v)=(dist(u,v)+(1−qu.v)xv)For fugitive:Qu(v)=∂qu,v∂F(u,v)=−pu,vxv
对
v
∈
L
∖
{
u
}
v\in L \setminus \{u\}
v∈L∖{u},
P
u
(
v
)
P_u(v)
Pu(v) 的值都应该相等, 否则警察选择调高梯度小的一侧会更优. 同理
Q
u
(
v
)
Q_u(v)
Qu(v) 对一个
u
u
u 也应该都相等.
1 = ∑ w ∈ L \ { u } p u , w = ∑ w ∈ L \ { u } p u , w x w ⋅ 1 x w = ∑ w ∈ L \ { u } p u , v x v ⋅ 1 x w = p u , v x v ⋅ ∑ w ∈ L \ { u } 1 x w . 1=\sum_{w\in L\backslash\{u\}}p_{u,w}=\sum_{w\in L\backslash\{u\}}p_{u,w}x_{w}\cdot{\frac{1}{x_{w}}}=\sum_{w\in L\backslash\{u\}}p_{u,v}x_{v}\cdot{\frac{1}{x_{w}}}=p_{u,v}x_{v}\cdot\sum_{w\in L\backslash\{u\}}{\frac{1}{x_{w}}}. 1=∑w∈L\{u}pu,w=∑w∈L\{u}pu,wxw⋅xw1=∑w∈L\{u}pu,vxv⋅xw1=pu,vxv⋅∑w∈L\{u}xw1.
故 p u , v = 1 / x v ∑ w ∈ L \ { u } 1 / x w . \large p_{u,v}={\frac{1/x_{v}}{\sum_{w\in L\backslash\{u\}}1/x_{w}}}. pu,v=∑w∈L\{u}1/xw1/xv.
由于
p
p
p 的纳什平衡点不依赖与
q
q
q, 所以我们不妨令
q
u
,
v
=
[
v
=
t
]
q_{u,v}=[v=t]
qu,v=[v=t] , 即 逃犯只会往
t
t
t 点跑.
F
(
u
,
v
)
=
1
/
x
v
∑
w
∈
L
∧
{
u
}
1
/
x
w
(
d
i
s
t
(
u
,
v
)
+
(
1
−
q
u
,
v
)
x
v
)
.
F
(
u
,
v
)
=
1
/
x
v
∑
w
∈
L
\
{
u
}
1
/
x
w
(
d
i
s
t
(
u
,
v
)
+
x
v
−
[
v
=
t
]
x
t
)
.
x
u
=
∣
L
∖
{
u
}
∣
−
1
+
∑
w
∈
L
\
{
u
}
d
i
s
(
u
,
w
)
∑
w
∈
L
\
{
u
}
1
/
x
w
x
u
⋅
∑
v
∈
L
\
{
u
}
1
/
x
v
=
∣
L
∣
−
2
+
[
u
∉
L
]
+
∑
v
∈
L
\
{
u
}
d
i
s
t
(
u
,
v
)
/
x
v
.
设
u
∈
L
,
将上式两边加
1.
x
u
⋅
∑
v
∈
L
1
/
x
v
=
∣
L
∣
−
1
+
∑
v
∈
L
d
i
s
t
(
u
,
v
)
/
x
v
.
x
u
=
∣
L
∣
−
1
+
∑
v
∈
L
d
i
s
t
(
u
,
v
)
/
x
v
∑
v
∈
L
1
/
x
v
.
设
y
u
=
1
/
x
u
,
y
u
=
∑
v
∈
L
y
v
∣
L
∣
−
1
+
∑
v
∈
L
d
i
s
t
(
u
,
v
)
y
v
.
F(u,v)=\frac{1/x_{v}}{\sum_{w\in L\land\{u\}}1/x_{w}}(\mathrm{dist}\,(u,v)+(1-q_{u,v})x_{v}).\\ F(u,v)=\frac{1/x_{v}}{\sum_{w\in L\backslash\{u\}}1/x_{w}}(\mathrm{dist}\ (u,v)+x_{v}-[v=t]x_{t}).\\ x_{u}=\frac{|L\setminus\{u\}|-1 +\sum_{w\in L\backslash\{u\}}dis(u,w)} {\sum_{w\in L\backslash\{u\}\ ~~1/x_{w}}} \\ x_{u}\cdot\sum_{v\in L\backslash\{u\}}1/x_{v}=|L|-2+[u\not \in L]+\sum_{v\in L\backslash\{u\}}\mathrm{dist}\,(u,v)/x_{v}. \\ 设 u\in L, 将上式两边加1. \\ x_{u}\cdot\sum_{v\in L}1/x_{v}=|L|-1+\sum_{v\in L}\mathrm{dist}\left(u,v\right)/x_{v}. \\ x_{u}=\dfrac{|L|-1+\sum_{v\in L}\mathrm{dist}\left(u,v\right)/x_{v}}{\sum_{v\in L}1/x_{v}}. \\ 设 y_u=1/x_u, y_{u}=\frac{\sum_{v\in L}y_{v}}{\vert L\vert-1+\sum_{v\in L}\mathrm{dist}\left(u,v\right)y_{v}}.
F(u,v)=∑w∈L∧{u}1/xw1/xv(dist(u,v)+(1−qu,v)xv).F(u,v)=∑w∈L\{u}1/xw1/xv(dist (u,v)+xv−[v=t]xt).xu=∑w∈L\{u} 1/xw∣L∖{u}∣−1+∑w∈L\{u}dis(u,w)xu⋅v∈L\{u}∑1/xv=∣L∣−2+[u∈L]+v∈L\{u}∑dist(u,v)/xv.设u∈L,将上式两边加1.xu⋅v∈L∑1/xv=∣L∣−1+v∈L∑dist(u,v)/xv.xu=∑v∈L1/xv∣L∣−1+∑v∈Ldist(u,v)/xv.设yu=1/xu,yu=∣L∣−1+∑v∈Ldist(u,v)yv∑v∈Lyv.
用上述
y
u
y_u
yu 的式子迭代即可. 计算完所有叶子节点的
y
y
y 后, 我们再求
x
s
x_s
xs.
复杂度 O ( n ℓ + q ℓ 2 + n ℓ ) = O ( n ℓ + q ℓ 2 ) , w h e r e n = ∣ V ∣ , ℓ = ∣ L ∣ a n d q i s t h e n u m b e r o f i t e r a t i o n s O(n\ell+q\ell^{2}+n\ell)=O\left(n\ell+q\ell^{2}\right),{\mathrm{where~}}n=|V|,\ell=|L|{\mathrm{~and~}}q{\mathrm{~is~the~number~of~iterations}} O(nℓ+qℓ2+nℓ)=O(nℓ+qℓ2),where n=∣V∣,ℓ=∣L∣ and q is the number of iterations
初始化 y u = 1 ( n − 1 ) 2 y_u=\dfrac 1 {(n-1)^2} yu=(n−1)21 , q q q 取 10000 即可轻松通过本题.
const int N = 102;
int n, m, id[N], A[N][N], d[N];
ld y[2][N];
VT<int> e[N];
void bfs(int x) {
fill_n(d, n, n);
d[x] = 0;
queue<int> q;
q.push(x);
while(q.size()) {
int x = q.front(); q.pop();
for(int y: e[x]) if(cmin(d[y], d[x] + 1))
q.push(y);
}
}
void solve() {
qr(n); m = 0;
fill_n(id, n, -1);
{
int x, y;
FOR(i, n - 1) {
qr(x, y);
x--; y--;
e[x].pb(y);
e[y].pb(x);
}
}
VT<int> L;
rep(i, 0, n - 1) if(SZ(e[i]) == 1) L.pb(i), id[i] = m++; // 叶子节点, 重编号
for(int x: L) {
bfs(x);
rep(i, 0, m - 1) A[id[x]][i] = d[L[i]];
}
fill_n(y[0], m, 1.0 / ((n - 1) * m));
int o = 0;
FOR(_, 10000) {
ld sum = accumulate(y[o], y[o] + m, 0.0);
rep(i, 0, m - 1) {
ld t = m - 1;
rep(j, 0, m - 1) t += A[i][j] * y[o][j];
y[!o][i] = sum / t;
}
o ^= 1;
}
int s; qr(s); s--;
ld ans = 0;
if(id[s] == -1) {
ans = m - 1;
bfs(s);
rep(i, 0, m - 1) ans += y[o][i] * d[L[i]];
ans /= accumulate(y[o], y[o] + m, (ld)0.0);
}
else ans = 1 / y[o][id[s]];
pr2(ans);
}
技巧四: 独立变量与期望
对独立随机变量 x , y x,y x,y, 有: E ( x ⋅ y ) = E ( x ) ⋅ E ( y ) E(x\cdot y)=E(x)\cdot E(y) E(x⋅y)=E(x)⋅E(y).
例题1: 1842G - Tenzing and Random Operations
Given an array a a a of length n n n and an integer v v v.
Tenzing will perform the following operation m m m times:
- Choose an integer i i i such that 1 ≤ i ≤ n 1 \leq i \leq n 1≤i≤n uniformly at random.
- For all j j j such that i ≤ j ≤ n i \leq j \leq n i≤j≤n, set a j : = a j + v a_j := a_j + v aj:=aj+v.
Tenzing wants to know the expected value of ∏ i = 1 n a i \prod_{i=1}^n a_i ∏i=1nai after performing the m m m operations, modulo 1 0 9 + 7 10^9+7 109+7.
设
d
j
,
i
d_{j,i}
dj,i 表示第
j
j
j 次对
a
i
a_i
ai 的增量(取值 0 or v)
A
n
s
=
∏
i
=
1
n
(
a
i
+
∑
j
=
1
m
d
j
,
i
)
注意到若
j
≠
k
,
则
d
j
,
d
k
独立
.
若
l
<
r
,
E
(
d
j
,
l
⋅
d
j
,
r
)
=
E
(
d
j
,
l
)
⋅
v
,
即同类
(
same j
)
只由首项取值决定
Ans=\prod_{i=1}^n (a_i + \sum_{j=1}^m d_{j,i}) \\ 注意到若 j\ne k, 则d_j, d_k 独立. \\ 若l<r, E(d_{j,l}\cdot d_{j,r})=E(d_{j,l}) \cdot v, 即同类(\text {same ~j})只由首项取值决定 \\
Ans=i=1∏n(ai+j=1∑mdj,i)注意到若j=k,则dj,dk独立.若l<r,E(dj,l⋅dj,r)=E(dj,l)⋅v,即同类(same j)只由首项取值决定
定义$ f[i][j]$ 表示 前$ i$ 项含有$ j
个
∗
∗
不同
∗
∗
类的
个**不同**类的
个∗∗不同∗∗类的d$变量即可, 时间复杂度
O
(
n
⋅
min
(
n
,
m
)
)
O(n\cdot \min(n,m))
O(n⋅min(n,m)). 空间复杂度
O
(
n
)
O(n)
O(n).
int n, m;
ll f[N], v;
void solve() {
qr(n, m, v);
ll x, invn = INV(n, mod);
f[0] = 1;
FOR(i, n) {
qr(x);
REP(j, 0, min(m, i - 1)) {
ad(f[j + 1], f[j] * (m - j) % mod * i % mod * invn % mod * v % mod);
f[j] = (x + j * v) % mod * f[j] % mod;
}
}
ll ans = 0;
REP(i, 0, min(n, m)) ad(ans, f[i]);
pr2(ans);
}
交互题
交互题一般的突破口找到某种关键结构, 通过这个关键结构来实现高效求解…
设找到其的概率为 p p p, 则期望进行 1 / p 1/p 1/p 次伯努利实验才能找到.
假设我们进行 t t t 次, 则都失败的概率为 ( 1 − p ) t (1-p)^t (1−p)t, 我们希望失败概率极小, 不妨令容错概率为 1 0 − 6 10^{-6} 10−6, 则 ( 1 − p ) t < 1 0 − 6 → t > − 6 lg ( 1 − p ) = − 6 ln 10 ln ( 1 − p ) ≈ 2.3 × 6 p (1-p)^t < 10^{-6} \rightarrow t >\frac{-6}{\lg (1-p)} = \frac{-6\ln 10}{\ln(1-p)}\approx \frac{2.3 \times 6}p (1−p)t<10−6→t>lg(1−p)−6=ln(1−p)−6ln10≈p2.3×6.
简单来说, 进行 == 10 / p 10/p 10/p==次, 出错概率 < 1 0 − 4 <10^{-4} <10−4.
下面给出 p − t p-t p−t 表( t = ⌈ − 6 lg ( 1 − p ) ⌉ ) t=\lceil \frac{-6}{\lg (1-p)}\rceil) t=⌈lg(1−p)−6⌉):
p p p | t t t |
---|---|
1/2 | 20 |
1/5 | 62 |
1/10 | 132 |
1/40 | 546 |
1/100 | 1375 |
1/1000 | 13809 |
鸽巢定理例题
n ( 3 ≤ n ≤ 5000 ) n(3\le n\le 5000) n(3≤n≤5000) 个隐藏整数 a i ( i ∈ [ 1 , n ] , a i ≤ [ 1 , 4 ] ) a_i(i\in [1, n], a_i\le [1,4]) ai(i∈[1,n],ai≤[1,4]), 你需要在 5500次询问内确定整个序列 / 报告这个序列不能唯一确定.
每次询问给出 i , j , k ( i ≠ j , i ≠ k , j ≠ k , i , j , k ∈ [ 1 , n ] ) i,j,k(i\ne j,i\ne k,j\ne k,i,j,k\in [1,n]) i,j,k(i=j,i=k,j=k,i,j,k∈[1,n]),
- 若 a i , a j , a k a_i,a_j,a_k ai,aj,ak 可以构成非退化三角形, 则返回 16 s 2 16s^2 16s2, s s s 为三角形面积.
- 否则, 返回0.
由于返回 16 s 2 = ( a + b + c ) ∗ ( b + c − a ) ∗ ( a + b − c ) ∗ ( a + c − b ) 16s^2=(a+b+c)*(b+c-a)*(a+b-c)*(a+c-b) 16s2=(a+b+c)∗(b+c−a)∗(a+b−c)∗(a+c−b).(海伦公式)
所以我们先打表所有可以唯一确定的三元组.
观察1: (1,4,4), (2,2,3) 具有相同的面积(返回 63). 其他有序三元组的面积都不同.
观察2: (2,2,4) 不合法. 其他不合法三元组最小值是1.
-
n ≤ 8 n \le 8 n≤8: 询问所有 ( i , j , k ) (i,j,k) (i,j,k). 然后 4 n 4^n 4n枚举所有状态判断.
-
n > 8 n > 8 n>8: 由鸽巢定理, 一定存在等边三角形. ( n > 8 = 2 ∗ 4 n>8=2*4 n>8=2∗4)
-
如果找到 ( a , a , a ) , a > 1 (a,a,a), a>1 (a,a,a),a>1, 则剩下的所有点都可以一次判断( ( a , a , b ) (a,a,b) (a,a,b)式询问).
-
如果找到(1,1,1), 它可以用了分别=1, >1的两种情况.
当 > 1 >1 >1的数超过6个时,根据鸽巢原理, 还会存在 ( a , a , a ) ( a > 1 ) (a,a,a)(a>1) (a,a,a)(a>1)的等边三角形. 这时候可以退化为 2.a
当 > 1 >1 >1的数不超过6个时, 询问所有相互之间的关系, 然后枚举所有状态判断. O ( 3 k ) O(3^k) O(3k) (值域为 [ 2 , 4 ] [2,4] [2,4] ). 需要在注意应该多增加一个 = 1 =1 =1 的数来辅助(增加一些和1的询问), 因为 1 1 1 可以帮助找出相等的位置 ( 1 , 2 , 2 ) (1,2,2) (1,2,2)
-
至于怎么找等边三角形:
- 当 n ≤ 30 n\le 30 n≤30, 枚举所有三元组.
- 否则, 随机三元组询问即可, 不难发现当 1~4是均匀分布时, 等边三角形的数量最少, 此时采样成功概率 p = ( 1 / 4 ) 2 = 1 / 16 p=(1/4)^2=1/16 p=(1/4)2=1/16, 故 160次以内高概率找得到.
int n, s[5][5][5], cnt[N];
// #define DE
#ifdef DE
int ans[N];
#endif
int ask(int i, int j, int k) {
cout << "? " << i << ' ' << j << ' ' << k << endl;
#ifdef DE
return s[ans[i]][ans[j]][ans[k]];
#endif
qr(i);
return i;
}
int a[N], msk[N], b[10], val[10], m, v[10][10][10], ok, same[N];
void dfs(int i, int st) {
if(i == m + 1 || (st == 2 && i == m)) {
FOR(i, m) rep(j, i + 1, m) rep(k, j + 1, m)
if(v[i][j][k] != s[val[i]][val[j]][val[k]]) return;
if(++ok > 1) {
cout << "! -1" << endl;
exit(0);
}
FOR(i, m) a[b[i]] = val[i];
return;
}
rep(j, st, 4) val[i] = j, dfs(i + 1, st);
}
void out() {
cout << "! ";
FOR(i, n) cout << a[i] << " \n"[i == n];
cout.flush();
#ifdef DE
FOR(i, n) assert(a[i] == ans[i]);
#endif
}
void vio(int st) {
FOR(i, m) rep(j, i + 1, m) rep(k, j + 1, m)
v[i][j][k] = ask(b[i], b[j], b[k]);
dfs(1, st);
assert(ok == 1);
out();
}
void solve(int x, int y, int z) {
assert(x > 0 && a[x] > 1);
int d = a[x];
FOR(i, 4) same[s[d][d][i]] = i; // guess number
FOR(i, n) if(!a[i]) a[i] = same[ask(x, y, i)];
out();
}
void solve() {
n = 4;
FOR(i, n) rep(j, i, n) rep(k, j, n)
if(i + j > k) {
s[i][j][k] = s[i][k][j] = s[j][i][k] = s[j][k][i] = s[k][i][j] = s[k][j][i] =
(i + j + k) * (i + j - k) * (i + k - j) * (j + k - i);
cnt[s[i][j][k]]++;
if(k == i) same[s[i][j][k]] = i;
}
qr(n);
#ifdef DE
FOR(i, n) qr(ans[i]);
#endif
if(n <= 8) {
FOR(i, n) b[++m] = i;
vio(1);
}
else {
int x = -1, y, z;
if(n <= 30) {
FOR(i, n) rep(j, i + 1, n) rep(k, j + 1, n) {
int t = ask(i, j, k);
if(t = same[t]) {
a[i] = a[j] = a[k] = t;
x = i; y = j; z = k;
goto nxt;
}
}
}
else {
mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
auto rd = uniform_int_distribution<int>(1, n);
while(1) {
VT<int> v{rd(rng), rd(rng), rd(rng)};
sort(v);
if(v[0] == v[1] || v[1] == v[2]) continue;
int t = ask(v[0], v[1], v[2]);
if(t = same[t]) {
x = v[0]; y = v[1]; z = v[2];
a[x] = a[y] = a[z] = t;
goto nxt;
}
}
}
nxt:
assert(x != -1);
if(a[x] == 1) {
FOR(i, n) if(!a[i]) {
int t = ask(x, y, i);
if(t) a[i] = 1;
else {
b[++m] = i;
if(m == 7) {
FOR(u, m) rep(v, u + 1, m) rep(w, v + 1, m) {
t = ask(b[u], b[v], b[w]);
if(t = same[t]) {
a[b[u]] = a[b[v]] = a[b[w]] = t;
return solve(b[u], b[v], b[w]);
}
}
}
}
}
b[++m] = x; val[m] = 1;
vio(2);
}
else solve(x, y, z);
}
}
图与生成树
树是最简单的连通图, 给图的交互题, 可以试着先从生成树入手, 找到生成树后, 剩余的结构可能很好解决.
1819E - Roads in E City
参考博客题解 CF1819E 【Roads in E City】 - 洛谷专栏
主要思想:
由于已维修的道路构成连通块, 所以
-
如果 ( u , v ) (u,v) (u,v) 不是桥, 则不会影响其他点的连通性;
-
如果边 ( u , v ) (u,v) (u,v) 是桥, 那么断开后, 我们询问 k k k 次 u u u, k k k 次 v v v.
设 u u u 侧连通块大小为 t t t, 则 2 k 2k 2k 次询问均没有0的概率(假设题目的 s ∼ U [ 1 , n ] s\sim U[1,n] s∼U[1,n]) 为 ( t n ⋅ n − t n ) k ≤ 1 4 k \left(\dfrac {t}n \cdot \dfrac {n -t}n \right)^k\le \dfrac 1 {4^k} (nt⋅nn−t)k≤4k1.
如何找生成树? 只需要把不是桥的边删去即可, 最后一定会剩余 n − 1 n-1 n−1 条桥边. 这里次数为 O ( 2 k m ) O(2k m) O(2km)
剩余的边 ( s , t ) (s,t) (s,t), 我们只需删掉 s , t s,t s,t 生成树路径上的一条边, 再加入 ( s , t ) (s,t) (s,t) 看 s , t s,t s,t 是否连通即可. 这个次数也是 O ( 2 k m ) O(2km) O(2km).
总共大概需要 4 k m 4km 4km 次, 我们令 k = 20 k=20 k=20 即可.
const int N = 2010, K = 20;
int n, m, cut[N], pre[N], dep[N];
VT<pii> e[N];
int ask(int x) {
assert(1 <= x && x <= n);
cout << "? " << x << endl;
qr(x); return x;
}
void dfs(int x) {
for(auto [y, w]: e[x]) if(!dep[y]) {
dep[y] = dep[x] + 1;
pre[y] = w; // Edge_id
dfs(y);
}
}
void solve() {
qr(n, m);
FOR(i, n) e[i].clear();
int x, y;
VT<pii> E;
FOR(i, m) {
qr(x, y);
E.emplace_back(x, y);
}
auto check = [&](int i, bool ban) ->int { // bridge
if(ban) cout << "- " << i + 1 << endl; // ban
auto [x, y] = E[i];
FOR(i, K) {
if(!ask(x)) return 1;
if(!ask(y)) return 1;
}
return 0;
};
rep(i, 0, m - 1) {
if(cut[i] = check(i, 1)) {
cout << "+ " << i + 1 << endl; // recover
auto [x, y] = E[i];
e[x].pb({y, i + 1}),
e[y].pb({x, i + 1});
}
}
fill_n(dep + 1, n, 0);
dep[1] = 1; dfs(1);
rep(i, 0, m - 1) {
if(!cut[i]) {
auto [x, y] = E[i];
if(dep[x] < dep[y]) swap(x, y);
cout << "- " << pre[x] << endl;
cout << "+ " << i + 1 << endl;
cut[i] = !check(i, 0);
cout << "- " << i + 1 << endl;
cout << "+ " << pre[x] << endl;
}
}
cout << "! ";
rep(i, 0, m - 1) cout << cut[i] << " \n"[i == m - 1];
cout.flush(); qr(n); if(!n) exit(0);
}
简单DP
这类DP无需考虑后效性, 直接设计状态即可.
- Problem - 1835E - Codeforces: f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k] 表示已知 i i i 个有效键位, j j j 个无效键位, { k = 0 : 已找到删除键 ; k = 1 : 正在寻找删除键 ; k = 2 : 已找到删除键 . \begin{cases} k=0:已找到删除键; \\k=1:正在寻找删除键;\\k=2: 已找到删除键. \end{cases} ⎩ ⎨ ⎧k=0:已找到删除键;k=1:正在寻找删除键;k=2:已找到删除键. 的状态的概率.在需要回撤时直接 p ∗ c o s t p*cost p∗cost 即可计算期望开销.