题目
题目描述
三个好伙伴分赃款!小朋友不要学。
一开始三个人分别有 x , y , z x,y,z x,y,z 元。每一轮中,如果 x = y = z x=y=z x=y=z 则三人拿钱跑路,否则,钱最多的人拿出 1 1 1 元钱,随机给另外的俩人中的一个。如果有两个人都是钱最多的,那么二人会石头剪刀布决定谁给钱。石头剪刀布的获胜概率应当被视为 1 2 \frac{1}{2} 21 。
现在,请你求出三人期望经过多少轮才会跑路,或者输出 − 1 -1 −1 表示他们在被警察抓到之前根本不能分完钱——也就是这个过程会永远进行下去。
数据范围与提示
对于
70
%
70\%
70% 的数据,有
x
+
y
+
z
⩽
5
×
1
0
3
x+y+z\leqslant 5\times 10^3
x+y+z⩽5×103 。
对于
100
%
100\%
100% 的数据,有
x
+
y
+
z
⩽
1
0
6
x+y+z\leqslant 10^6
x+y+z⩽106 。这数据范围真友好。
思路
首先 d p \tt dp dp 是很容易想到的,明显可以只存差分数组。
鉴于我已看过题解,不妨设
z
z
z 为三者中最大的,将
a
=
z
−
x
a=z-x
a=z−x 与
b
=
z
−
y
b=z-y
b=z−y 作为自变量,那么有转移
f ( a , b ) = f ( a − 2 , b − 1 ) + f ( a − 1 , b − 2 ) 2 + 1 ( a , b ⩾ 2 ) f(a,b)=\frac{f(a{\it-}2,b{\it-}1){\it+}f(a{\it-}1,b{\it-}2)}{2}+1\quad(a,b\geqslant 2) f(a,b)=2f(a−2,b−1)+f(a−1,b−2)+1(a,b⩾2)
几个边界条件是,大小关系发生了变化的情况。
{ f ( a , 1 ) = f ( a − 2 , 0 ) + 2 ( a ⩾ 2 ) f ( a , 0 ) = f ( a − 1 , 1 ) + f ( a + 1 , 2 ) 2 + 1 ( a ⩾ 2 ) \begin{cases} f(a,1)=f(a{\it-}2,0)+2&(a\geqslant 2)\\ \;\\ f(a,0)=\frac{f(a-1,1)+f(a+1,2)}{2}+1&(a\geqslant 2)\\ \end{cases} ⎩⎪⎨⎪⎧f(a,1)=f(a−2,0)+2f(a,0)=2f(a−1,1)+f(a+1,2)+1(a⩾2)(a⩾2)
至于 f ( 1 , 1 ) , f ( 1 , 0 ) f(1,1),f(1,0) f(1,1),f(1,0) 呢?可以忽略,因为它们不可能到达 f ( 0 , 0 ) = 0 f(0,0)=0 f(0,0)=0 。如果你更细心些,你会想到 f ( 2 , 0 ) f(2,0) f(2,0) 也不存在。更一般地, f ( a , b ) f(a,b) f(a,b) 只在 a + b ≡ 0 ( m o d 3 ) a+b\equiv 0\pmod{3} a+b≡0(mod3) 时有定义。
我当时以为,这样 70 70 70 分就到手了。可是阴险的出题人在家门口埋了颗地雷——转移有环!比如 f ( a , 0 ) f(a,0) f(a,0) 和 f ( a + 1 , 2 ) f(a+1,2) f(a+1,2) 就可以互相转移。
考虑手推几步,去掉环。显然
a
,
b
⩾
2
a,b\geqslant 2
a,b⩾2 的情况并不产生环,所以只考虑
b
<
2
b<2
b<2 的情况。
2
×
f
(
a
,
0
)
=
f
(
a
−
1
,
1
)
+
f
(
a
+
1
,
2
)
+
2
=
f
(
a
−
1
,
1
)
+
f
(
a
,
0
)
+
f
(
a
−
1
,
1
)
2
+
3
⇒
f
(
a
,
0
)
=
f
(
a
−
1
,
1
)
+
2
\begin{aligned} 2\times f(a,0)&=f(a-1,1)+f(a+1,2)+2\\ &=f(a-1,1)+\frac{f(a,0)+f(a-1,1)}{2}+3\\ \Rightarrow f(a,0)&=f(a-1,1)+2 \end{aligned}\\
2×f(a,0)⇒f(a,0)=f(a−1,1)+f(a+1,2)+2=f(a−1,1)+2f(a,0)+f(a−1,1)+3=f(a−1,1)+2
甚至可以得寸进尺、得陇望蜀,我们把
f
(
a
−
1
,
1
)
f(a-1,1)
f(a−1,1) 再拆开,得到
f
(
a
,
0
)
=
f
(
a
−
3
,
0
)
+
4
⇒
f
(
a
,
0
)
=
4
a
3
(
a
m
o
d
3
=
0
)
⇒
f
(
a
,
1
)
=
4
a
−
2
3
(
a
m
o
d
3
=
2
)
\begin{aligned} f(a,0)&=f(a-3,0)+4\\ \Rightarrow f(a,0)&=\frac{4a}{3}\quad(a\bmod 3=0)\\ \Rightarrow f(a,1)&=\frac{4a-2}{3}\quad(a\bmod 3=2) \end{aligned}
f(a,0)⇒f(a,0)⇒f(a,1)=f(a−3,0)+4=34a(amod3=0)=34a−2(amod3=2)
至此,环已经不是问题。接下来,我们要向最后 30 30 30 分发起猛攻!
注意 a , b ≥ 2 a,b\ge 2 a,b≥2 时的递推式是很美的,很像 马走日字。我们知道,马走日字是可以用组合数直接算出走到另一个格子的方案数的。那么我们直接求出初始状态 f ( z 0 − x 0 , z 0 − y 0 ) f(z_0{\it-}x_0,z_0{\it-}y_0) f(z0−x0,z0−y0) 走到 f ( a , 0 / 1 ) f(a,0/1) f(a,0/1) 的 系数 是多少就行了!——走到 f ( 0 / 1 , a ) f(0/1,a) f(0/1,a) 同理。
也就是说, f ( a , b ) f(a,b) f(a,b) 不断用递推式往下递归,最后必然得到 f ( i , 0 / 1 ) f(i,0/1) f(i,0/1) 的线性和。我们要做的只是算出系数。即 ( 1 2 ) s t e p ({1\over 2})^{step} (21)step 乘方案数。
可是递推式中还要加一个常数呢!这个也挺好算,可以考虑每个位置的 + 1 +1 +1 在最终值里面的系数。显然是 ( 1 2 ) s t e p (\frac12)^{step} (21)step 。而走到 f ( a , 0 / 1 ) f(a,0/1) f(a,0/1) 之前,每个点有两个子节点(两次向下递归),这两个子节点(子状态)恰好分别提供系数 ( 1 2 ) s t e p + 1 (\frac12)^{step+1} (21)step+1,也就是说,一个状态贡献的常数是子状态贡献常数的和。
既然这样,我们可以考虑只计算叶子节点(图的终止节点)的常数贡献的系数。又是计算系数啊,你好烦啊。叶子节点的贡献是
(
1
2
)
s
t
e
p
(\frac12)^{step}
(21)step,一共有
s
t
e
p
step
step 个节点会加上该点的贡献(因为叶子节点自身是没有常数贡献的),显然还需要乘路径数量。
复杂度 O [ max ( x , y , z ) ] \mathcal O[\max(x,y,z)] O[max(x,y,z)] 。常数什么的我们从来不在乎的。
代码
小坑点:由于 f ( a , 1 ) f(a,1) f(a,1) 是单独算的,它不能为 f ( a − 2 , 0 ) f(a-2,0) f(a−2,0) 提供系数。
#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
typedef long long int_;
inline int readint(){
int a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
(c == '-' ? f = -f : 0);
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
const int Mod = 998244353;
const int MaxN = 1000010;
const int_ inv2 = (Mod+1)>>1;
int_ jc[MaxN], inv[MaxN];
int bin[MaxN]; // bin[x] = 2^{-x}
void prepare(int n){
bin[0] = 1; // 2^0 = 1
for(int i=1; i<MaxN; ++i)
bin[i] = (bin[i-1]*inv2)%Mod;
jc[1] = inv[1] = 1;
for(int i=2; i<=n; ++i){
inv[i] = (Mod-Mod/i)*inv[Mod%i]%Mod;
jc[i] = i*jc[i-1]%Mod;
}
for(int i=2; i<=n; ++i)
inv[i] = inv[i-1]*inv[i]%Mod;
inv[0] = jc[0] = 1;
}
int_ C(int n,int m){
if(m < 0 || n < m) return 0;
return jc[n]*inv[m]%Mod*inv[n-m]%Mod;
}
int solve(int x,int y){
if((x+y)%3) return -1; // 无解
if(x > y) swap(x,y); // 方便些
if(x == 0) return (y/3)*4;
if(x == 1) return (4*y-2)/3;
int ans = 0; // 最终答案
for(int i=y/3*3; i; i-=3){
/* (i,0) 的贡献 */ ;
int k = (x+y-i)/3; // 行走步数
int_ c = C(k-1,y-k-1); // = C(k,y-k)-C(k-1,y-k); // 去掉从 (i+2,1) 到来的
c = c*bin[k]%Mod; // 每次都会带上系数 1/2
ans = (ans+c*(i/3)*4)%Mod; // dp 值的贡献
ans = (ans+c*k)%Mod; // 常数的贡献
/* (0,i) 的贡献 */ ;
c = C(k-1,x-k-1); // = C(k,x-k)-C(k-1,x-k); // 去掉从 (1,i+2) 到来的
c = c*bin[k]%Mod; // 每次都会带上系数 1/2
ans = (ans+c*(i/3)*4)%Mod; // dp 值的贡献
ans = (ans+c*k)%Mod;
}
for(int i=(y-2)/3*3+2; i>=0; i-=3){
/* (i,1) 的贡献 */ ;
int k = (x+y-i-1)/3; // 行走步数
int_ c = C(k,y-k-1)*bin[k]%Mod;
ans = (ans+c*(4*i-2)/3)%Mod; // dp 贡献
ans = (ans+c*k)%Mod; // 常数贡献
/* (1,i) 的贡献 */ ;
c = C(k,x-k-1)*bin[k]%Mod; // 不需要减掉啥了
ans = (ans+c*(4*i-2)/3)%Mod;
ans = (ans+c*k)%Mod;
}
return ans;
}
int main(){
prepare(MaxN-1); // don't forget it!
int T = readint();
for(int x,y,z; T; --T){
x = readint(), y = readint();
z = readint();
if(x > z) swap(x,z); // 选择排序
if(y > z) swap(y,z);
printf("%d\n",solve(z-x,z-y));
}
return 0;
}