LCT好强啊。
题目
T1:BZOJ 1004 / HNOI 2008 Cards (cards)(Burnside引理+DP)
T2:BZOJ 2002 / HNOI 2010 弹飞绵羊 (bounce)(LCT)
T3:BZOJ 4008 / HNOI 2015 亚瑟王 (arthur)(概率DP)
今天的题目都是湖南省选,而且在BZOJ上的题号的中间两位都是0,这是爆零的诅咒吧?
T1
分析
加上一个置换
123...n
1
2
3
.
.
.
n
,形成
m+1
m
+
1
个置换。
根据Burnside引理,答案等于:
D(ax) D ( a x ) 表示在置换 ax a x (第 x x 个置换)下,不变的元素(染色方案)个数。
对于每个,怎么求 D(ax) D ( a x ) 呢?
首先把 ax a x 分循环节。可以得出,一种方案在置换 ax a x 下不变,等价于这种方案在 ax a x 的每个循环节内只有一种颜色。
如一种置换为 24513 2 4 5 1 3 ,循环节为 (1,2,4)(3,5) ( 1 , 2 , 4 ) ( 3 , 5 ) ,那么一种方案在置换 ax a x 下不变,等价于这种方案的第 1 1 张,第张和第 4 4 张牌同色,第张牌和第 5 5 张牌同色。
然后?DP!
设表示到了第 k k 个循环节,用了张红色 j j 张蓝色的方案数。边界。转移就是枚举第 k k 个循环节被染成的颜色(表示第 k k 个循环节包含的牌数):
这样, D(ax) D ( a x ) 就等于 f[ax f [ a x 的循环节个数 ][Sr][Sb] ] [ S r ] [ S b ] 。
Source
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
int res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
const int N = 66, M = 23;
int n, Sr, Sb, Sg, m, MX, to[N][N], f[N][M][M];
bool vis[N];
int qpow(int a, int b) {
int res = 1; while (b) b & 1 ? res = res * a % MX : 0,
a = a * a % MX, b >>= 1; return res;
}
int main() {
int i, j, k, h; n = (Sr = read()) + (Sb = read()) + (Sg = read());
m = read(); MX = read(); for (i = 1; i <= m; i++) for (j = 1; j <= n; j++)
to[i][j] = read(); m++; for (j = 1; j <= n; j++) to[m][j] = j;
int ans = 0; for (i = 1; i <= m; i++) {
memset(vis, 0, sizeof(vis)); memset(f, 0, sizeof(f)); f[0][0][0] = 1;
int t = 0; for (j = 1; j <= n; j++) if (!vis[j]) {
int x = to[i][j], tot = 1; vis[j] = 1; t++;
while (x != j) vis[x] = 1, x = to[i][x], tot++;
for (k = 0; k <= Sr; k++) for (h = 0; h <= Sb; h++) {
if (k >= tot)
f[t][k][h] = (f[t][k][h] + f[t - 1][k - tot][h]) % MX;
if (h >= tot)
f[t][k][h] = (f[t][k][h] + f[t - 1][k][h - tot]) % MX;
f[t][k][h] = (f[t][k][h] + f[t - 1][k][h]) % MX;
}
}
ans = (ans + f[t][Sr][Sb]) % MX;
}
cout << ans * qpow(m, MX - 2) % MX << endl;
return 0;
}
T2
分析
以下,把装置编号视为从
1
1
开始计数,并且每一个操作的参数加
1
1
。
建立一个虚拟节点(绵羊到达节点
n+1
n
+
1
即被弹飞),维护一个LCT。首先利用
Link
L
i
n
k
(连边)操作建树,即:
1、如果
i+ki≤n
i
+
k
i
≤
n
,则
Link(i,i+ki)
L
i
n
k
(
i
,
i
+
k
i
)
。
2、否则
Link(i,n+1)
L
i
n
k
(
i
,
n
+
1
)
。
对于操作2,也就是
Cut
C
u
t
(删掉)一条边再加一条边。具体方法为:
1、如果
j+kj≤n
j
+
k
j
≤
n
,则
Cut(j,j+kj)
C
u
t
(
j
,
j
+
k
j
)
,否则
Cut(j,n+1)
C
u
t
(
j
,
n
+
1
)
。
2、如果
j+k≤n
j
+
k
≤
n
,则
Link(j,j+k)
L
i
n
k
(
j
,
j
+
k
)
,否则
Link(j,n+1)
L
i
n
k
(
j
,
n
+
1
)
。
3、把
kj
k
j
改为
k
k
。
对于操作1,则先执行和
Access(n+1)
A
c
c
e
s
s
(
n
+
1
)
,并把
n+1
n
+
1
Splay到对应平衡树的根。此时询问结果就是平衡树中节点
n+1
n
+
1
的子树大小减
1
1
。
Source
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
int res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
const int N = 2e5 + 5;
int n, m, nxt[N], fa[N], lc[N], rc[N], sze[N], rev[N], len, que[N];
int which(int x) {return rc[fa[x]] == x;}
bool isRoot(int x) {
return !fa[x] || (lc[fa[x]] != x && rc[fa[x]] != x);
}
void upt(int x) {
sze[x] = 1;
if (lc[x]) sze[x] += sze[lc[x]];
if (rc[x]) sze[x] += sze[rc[x]];
}
void down(int x) {
if (rev[x]) {
swap(lc[x], rc[x]);
if (lc[x]) rev[lc[x]] ^= 1;
if (rc[x]) rev[rc[x]] ^= 1;
rev[x] = 0;
}
}
void rotate(int x) {
int y = fa[x], z = fa[y], b = lc[y] == x ? rc[x] : lc[x];
if (z && !isRoot(y)) (lc[z] == y ? lc[z] : rc[z]) = x;
fa[fa[y] = x] = z; if (b) fa[b] = y;
if (lc[y] == x) rc[x] = y, lc[y] = b;
else lc[x] = y, rc[y] = b; upt(y); upt(x);
}
void splay(int x) {
int i, y; que[len = 1] = x;
for (y = x; !isRoot(y); y = fa[y]) que[++len] = fa[y];
for (i = len; i; i--) down(que[i]);
while (!isRoot(x)) {
if (!isRoot(fa[x])) {
if (which(x) == which(fa[x])) rotate(fa[x]);
else rotate(x);
}
rotate(x);
}
}
void Access(int x) {
int y; for (y = 0; x; y = x, x = fa[x]) {
splay(x); rc[x] = y; if (y) fa[y] = x; upt(x);
}
}
void MakeRoot(int x) {
Access(x); splay(x); rev[x] ^= 1;
}
void Link(int x, int y) {
MakeRoot(x); fa[x] = y;
}
void Cut(int x, int y) {
MakeRoot(x); Access(y); splay(y); lc[y] = fa[x] = 0; upt(y);
}
int Query(int x, int y) {
MakeRoot(x); Access(y); splay(y); return sze[y];
}
int main() {
int i, op, x, y; n = read(); for (i = 1; i <= n; i++) nxt[i] = read();
for (i = 1; i <= n + 1; i++) sze[i] = 1;
for (i = 1; i <= n; i++) Link(i, min(i + nxt[i], n + 1));
m = read(); while (m--) {
op = read(); x = read() + 1; if (op == 2)
y = read(), Cut(x, min(x + nxt[x], n + 1)),
Link(x, min(x + y, n + 1)), nxt[x] = y;
else printf("%d\n", Query(x, n + 1) - 1);
}
return 0;
}
T3
分析
如果能够求出每张卡牌在所有轮中被发动的概率
g[]
g
[
]
,那么答案显然为:
∑ni=1g[i]×d[i]
∑
i
=
1
n
g
[
i
]
×
d
[
i
]
第一步推出,
g[1]=1−(1−p[1])r
g
[
1
]
=
1
−
(
1
−
p
[
1
]
)
r
。
再考虑第二张:
情况一:如果第
1
1
张牌没有发动过技能,那么第张牌发动技能的概率为
1−(1−p[2])r
1
−
(
1
−
p
[
2
]
)
r
。
情况二:如果第
1
1
张牌发动过次技能,那么在第
1
1
张牌发动技能的那一轮,第张牌绝对不会再发动技能了,因此第
2
2
张牌发动技能的概率为。
结合这个例子,可以得到,对于任意的
i>1
i
>
1
,在第
1
1
张牌到第张牌在所有
r
r
轮内是否发动技能已经确定的情况下,第张牌被发动技能的概率只取决于第
1
1
张牌到第张牌中有多少张发动了技能。即如果有
j
j
张发动了技能,那么在此情况下第张牌发动技能的概率为
1−(1−p[i])r−j
1
−
(
1
−
p
[
i
]
)
r
−
j
。
根据这个性质,就能想到一个DP模型:
f[i][j]
f
[
i
]
[
j
]
表示前
i
i
张牌中,恰好有张在所有
r
r
轮中被发动过的概率。
转移就比较好想了。分第张牌发动与不发动两种情况:
1:发动。那么前
i−1
i
−
1
张牌一定有
j−1
j
−
1
张牌被发动技能,因此对于第
i
i
张牌,在轮中有
j−1
j
−
1
轮已经不会再发动技能了。所以:
f[i][j]+=(1−(1−p[i])r−j+1)×f[i−1][j−1]
f
[
i
]
[
j
]
+
=
(
1
−
(
1
−
p
[
i
]
)
r
−
j
+
1
)
×
f
[
i
−
1
]
[
j
−
1
]
2:不发动。那么前
i−1
i
−
1
张牌中一定有
j
j
张牌被发动技能,因此对于第张牌,在
r
r
轮中有轮是绝对不会再发动技能的。所以:
f[i][j]+=(1−p[i])r−j×f[i−1][j]
f
[
i
]
[
j
]
+
=
(
1
−
p
[
i
]
)
r
−
j
×
f
[
i
−
1
]
[
j
]
因此,完整的转移方程为:
f[i][j]=((1−(1−p[i])r−j+1)×f[i−1][j−1])[j>0]
f
[
i
]
[
j
]
=
(
(
1
−
(
1
−
p
[
i
]
)
r
−
j
+
1
)
×
f
[
i
−
1
]
[
j
−
1
]
)
[
j
>
0
]
+((1−p[i])r−j×f[i−1][j])[i≠j]
+
(
(
1
−
p
[
i
]
)
r
−
j
×
f
[
i
−
1
]
[
j
]
)
[
i
≠
j
]
那么求
g
g
就更容易了:
Source
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 224, R = 136;
int n, r, d[N]; double p[N], f[N][N], g[N], pw[N][R];
void work() {
memset(f, 0, sizeof(f)); memset(g, 0, sizeof(g));
int i, j; scanf("%d%d", &n, &r);
for (i = 1; i <= n; i++) scanf("%lf%d", &p[i], &d[i]), pw[i][0] = 1;
for (i = 1; i <= n; i++) for (j = 1; j <= r; j++)
pw[i][j] = pw[i][j - 1] * (1.0 - p[i]); f[0][0] = 1;
for (i = 1; i <= n; i++) for (j = 0; j <= min(i, r); j++) {
if (j) f[i][j] += f[i - 1][j - 1] * (1.0 - pw[i][r - j + 1]);
if (i != j) f[i][j] += f[i - 1][j] * pw[i][r - j];
}
for (i = 1; i <= n; i++) for (j = 0; j <= min(i - 1, r); j++)
g[i] += f[i - 1][j] * (1.0 - pw[i][r - j]); double ans = 0;
for (i = 1; i <= n; i++) ans += g[i] * d[i]; printf("%.10lf\n", ans);
}
int main() {
int T; cin >> T; while (T--) work();
return 0;
}