[题解]CLYZ2018省选训(bao)练(zha)模拟赛 Day 6

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引理,答案等于:

1m+1x=1m+1D(ax) 1 m + 1 ∑ x = 1 m + 1 D ( a x )

D(ax) D ( a x ) 表示在置换 ax a x (第 x x 个置换)下,不变的元素(染色方案)个数。
对于每个1xm+1,怎么求 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 张,第2张和第 4 4 张牌同色,第3张牌和第 5 5 张牌同色。
然后?DP!
f[k][i][j]表示到了第 k k 个循环节,用了i张红色 j j 张蓝色的方案数。边界f[0][0][0]=1。转移就是枚举第 k k 个循环节被染成的颜色(totk表示第 k k 个循环节包含的牌数):
f[k][i][j]=f[k1][itotk][j]+f[k1][i][jtotk]+f[k1][i][j]

这样, 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 开始计数,并且每一个操作的j参数加 1 1
建立一个虚拟节点n+1(绵羊到达节点 n+1 n + 1 即被弹飞),维护一个LCT。首先利用 Link L i n k (连边)操作建树,即:
1、如果 i+kin 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+kjn 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+kn 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,则先执行MakeRoot(j) 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

分析

如果能够求出每张卡牌在所有r轮中被发动的概率 g[] g [ ] ,那么答案显然为:
ni=1g[i]×d[i] ∑ i = 1 n g [ i ] × d [ i ]
第一步推出, g[1]=1(1p[1])r g [ 1 ] = 1 − ( 1 − p [ 1 ] ) r
再考虑第二张:
情况一:如果第 1 1 张牌没有发动过技能,那么第2张牌发动技能的概率为 1(1p[2])r 1 − ( 1 − p [ 2 ] ) r
情况二:如果第 1 1 张牌发动过1次技能,那么在第 1 1 张牌发动技能的那一轮,第2张牌绝对不会再发动技能了,因此第 2 2 张牌发动技能的概率为1(1p[2])r1
结合这个例子,可以得到,对于任意的 i>1 i > 1 ,在第 1 1 张牌到第i1张牌在所有 r r 轮内是否发动技能已经确定的情况下,第i张牌被发动技能的概率只取决于第 1 1 张牌到第i1张牌中有多少张发动了技能。即如果有 j j 张发动了技能,那么在此情况下第i张牌发动技能的概率为 1(1p[i])rj 1 − ( 1 − p [ i ] ) r − j
根据这个性质,就能想到一个DP模型:
f[i][j] f [ i ] [ j ] 表示前 i i 张牌中,恰好有j张在所有 r r 轮中被发动过的概率。
转移就比较好想了。分第i张牌发动与不发动两种情况:
1:发动。那么前 i1 i − 1 张牌一定有 j1 j − 1 张牌被发动技能,因此对于第 i i 张牌,在r轮中有 j1 j − 1 轮已经不会再发动技能了。所以:
f[i][j]+=(1(1p[i])rj+1)×f[i1][j1] f [ i ] [ j ] + = ( 1 − ( 1 − p [ i ] ) r − j + 1 ) × f [ i − 1 ] [ j − 1 ]
2:不发动。那么前 i1 i − 1 张牌中一定有 j j 张牌被发动技能,因此对于第i张牌,在 r r 轮中有j轮是绝对不会再发动技能的。所以:
f[i][j]+=(1p[i])rj×f[i1][j] f [ i ] [ j ] + = ( 1 − p [ i ] ) r − j × f [ i − 1 ] [ j ]
因此,完整的转移方程为:
f[i][j]=((1(1p[i])rj+1)×f[i1][j1])[j>0] f [ i ] [ j ] = ( ( 1 − ( 1 − p [ i ] ) r − j + 1 ) × f [ i − 1 ] [ j − 1 ] ) [ j > 0 ]
+((1p[i])rj×f[i1][j])[ij] + ( ( 1 − p [ i ] ) r − j × f [ i − 1 ] [ j ] ) [ i ≠ j ]
那么求 g g 就更容易了:
g[i]=j=0min(i1,r)(1(1p[i])rj)×f[i1][j]

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值