[LOJ#2327]「清华集训 2017」福若格斯

[LOJ#2327]「清华集训 2017」福若格斯

试题描述

小d是4xx9小游戏高手。

有一天,小d发现了一个很经典的小游戏:跳青蛙。

游戏在一个 \(5\) 个格子的棋盘上进行。在游戏的一开始,最左边的两个格子上各有一个向右的青蛙,最右边的两个格子上各有一个向左的青蛙。

每次移动可以选取一个青蛙,向这只青蛙的前方移动一格到空格子中或跳过前方的一个不同朝向的青蛙并移动到空格子中。

为了使你更好地理解这个游戏,我们下发了一个游戏demo作为参考(注意:这个demo中的棋盘大小和题目中并不相同)。

这个游戏本身当然难不倒小d,小d轻松地就解决了这个游戏。但是一个人玩游戏实在是太寂寞了,于是小d找到了小m和他一起玩耍。小d规定,自己只能操控向右的青蛙,小m只能操控向左的青蛙。

小d很快发现,这个游戏想要做到双方轮流行动,就无法达到交换所有青蛙的游戏结局。于是,小d打开了 \(m\) 个游戏,并规定双方轮流行动,每次选择其中一个游戏并控制自己的青蛙行动一步(不能不动)。小d发现,这么做的话就能够使大部分的游戏最终都交换所有的青蛙了。

由于小d是坑队友高手,所以他们玩了一会之后,就开始互相坑害对方,都希望使对方无法行动。他们约定,当轮到一方行动时,若其所有的青蛙都无法行动,则对方获得游戏的胜利。正当博弈论大师小d计算着谁会成为最后的胜者时,电脑卡死了。小d发现,只能kill掉一些游戏才能使剩下的游戏进行下去了。由于电脑已经卡死了,小d无法自由选择kill掉哪些游戏,只能运行系统自带的随机kill小程序。具体来说,小d运行这个随机kill小程序之后,每个游戏有 \(\frac{1}{2}\) 的概率被kill掉,有 \(\frac{1}{2}\) 的概率能够继续下去。游戏之间被kill掉的概率是独立的。

小d思考了一番,决定如果运行小程序之后他的胜率过低,就直接重启电脑。这时,小d突然发现自己已经不记得刚才轮到谁行动了,于是他决定综合考虑自己先手和后手的胜率。

小d并不擅长概率论,他想让你告诉他运行小程序后,剩下的局面为小d必胜、小m必胜、先手必胜、后手必胜的概率各为多少,这样他才能更好地决定是否重启电脑。

为了避免精度问题,输出答案乘 \(2^m\) 之后对 \(998244353\) 取模的结果。

注意:题目并不保证输入的所有m个状态中小m和小d之前的总行动步数相差不超过 \(1\),但是保证不会出现起始状态无法到达的状态。

输入

从标准输入读入数据。

我们使用一个长度为5的字符串来表示一个状态,其中,L 表示面朝右的青蛙,R 表示面朝左的青蛙,_(下划线)表示空格子。例如,初始状态为 LL_RR

本题含有多组数据,第一行两个整数 \(T,C(1\leq C\leq 100)\) 分别表示测试点编号和数据组数。

对于每组数据,第一行一个整数 \(n(1\leq n\leq 23)\) 表示不同状态的棋盘个数,接下来 \(n\) 行每行一个长度为 \(5\) 的字符串 \(s_i\) 和一个正整数 \(a_i(1\leq a_i\leq 10^6)\),分别表示棋盘的状态和在该状态下的棋盘的个数。

保证输入的字符串合法且不重复。

输出

输出到标准输出。

定义 \(m = \sum a_i\)。

对于每组数据,输出一行四个整数,分别表示小d必胜(即L的控制方必胜)、小m必胜(即R的控制方必胜)、先手必胜、后手必胜的概率乘 \(2^m\) 之后对 \(998244353\) 取模的结果。

输入示例1

0 1
1
LL_RR 1

输出示例1

0 0 1 1

输入示例2

0 1
3
LRRL_ 1
LRR_L 1
LLR_R 1

输出示例2

4 2 0 2

输入示例3

0 1
1
LRRL_ 1000000

输出示例3

421273116 0 0 1

数据规模及约定

对于 \(100\%\) 的数据,$1\leq n\leq 23,1\leq m\leq 10^6,\(1\leq C\leq 100\),所有可能出现在该数据点的状态均为等概率出现(也就是说你可以认为最后一个点中每种状态的 \(a_i\) 之和大约为 \(10^8/23\))。

题解

其实这题我也没搞透。。。不知道 surreal number 会在什么情况下适用。。。

代码中 Creal()Cfake() 两个函数用到了一个结论:

\(\sum_{i=0}^{ \mathrm{min} \{a, b-k\} } { C_a^i \cdot C_b^{i+k} } = C_{a+b}^{a+k}\)

证明的话可以考虑两种不同的球,黑球和白球分别有 \(a\) 和 \(b\) 个,然后我们选择 \(i\) 个白球和 \(i+k\) 个黑球并将它们反色,然后搁回去,那么新序列中就是 \(a+b\) 个位置中选择 \(a+k\) 个位置放白球,得到了等式的右边。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <string>
#include <map>
using namespace std;
#define rep(i, s, t) for(int i = (s); i <= (t); i++)
#define dwn(i, s, t) for(int i = (s); i >= (t); i--)

int read() {
    int x = 0, f = 1; char c = getchar();
    while(!isdigit(c)){ if(c == ‘-‘) f = -1; c = getchar(); }
    while(isdigit(c)){ x = x * 10 + c - ‘0‘; c = getchar(); }
    return x * f;
}

#define maxn 25
#define maxm 2000010
#define MOD 998244353
#define oo 2147483647
#define LL long long

map <string, int> val;
map <int, int> tot;

int search(string s) {
    if(val.count(s)) return val[s];
    int L = oo, R = oo;
    rep(i, 0, 4) {
        string to;
        if(s[i] == ‘L‘) {
            to = s;
            if(i <= 3 && to[i+1] == ‘_‘) swap(to[i], to[i+1]), L = search(to);
            to = s;
            if(i <= 2 && to[i+1] == ‘R‘ && to[i+2] == ‘_‘) swap(to[i], to[i+2]), L = search(to);
        }
        if(s[i] == ‘R‘) {
            to = s;
            if(i >= 1 && to[i-1] == ‘_‘) swap(to[i], to[i-1]), R = search(to);
            to = s;
            if(i >= 2 && to[i-1] == ‘L‘ && to[i-2] == ‘_‘) swap(to[i], to[i-2]), R = search(to);
        }
    }
    int now = 0;
    if(L == 0 && R == oo) now = 2;
    if(L == oo && R == 0) now = -2;
    if(L == 0 && R == 2) now = 1;
    if(L == -2 && R == 0) now = -1;
    // 3 up, -3 down, 4 star.
    if(L == 0 && R == 4) now = 3;
    if(L == 4 && R == 0) now = -3;
    if((L == R && R == 0) || (L == 3 && R == -3)) now = 4;
    // These are the states that can be found in the solution.(See the state-tree)
    return val[s] = now;
}

char str[maxn];

int Pow(int a, int b) {
    int ans = 1, t = a;
    while(b) {
        if(b & 1) ans = (LL)ans * t % MOD;
        t = (LL)t * t % MOD; b >>= 1;
    }
    return ans;
}

int fac[maxm], inv[maxm];
LL C(int n, int m) { return (LL)fac[n] * inv[m] % MOD * inv[n-m] % MOD; }

LL t_1, t0, t1, A[maxm];
void Creal(int s_2, int s_1, int s1, int s2) {
    t_1 = t0 = t1 = 0;
    rep(i, 0, s_1 + s1) A[i] = C(s_1 + s1, i);
    rep(i, 1, s_1 + s1) (A[i] += A[i-1]) %= MOD;
    rep(i, 0, s_2 + s2) {
        int k = (i - s_2 << 1);
        LL now = C(s_2 + s2, i);
        if(k < -s1) t_1 += now * A[s_1+s1] % MOD; // all negative
        else if(k > s_1) t1 += now * A[s_1+s1] % MOD; // all positive
        else {
            t0 += now * (A[s_1-k] - (s_1 - k == 0 ? 0 : A[s_1-k-1]) + MOD) % MOD;
            t_1 += now * (s_1 - k == 0 ? 0 : A[s_1-k-1]) % MOD;
            t1 += now * (A[s_1+s1] - A[s_1-k] + MOD) % MOD;
        }
    }
    t_1 %= MOD; t0 %= MOD; t1 %= MOD;
    return ;
}
LL f_2, f_1, f0, f1, f2;
void Cfake(int s_1, int s1) {
    f_2 = f_1 = f0 = f1 = f2 = 0;
    rep(i, 0, s_1 + s1) {
        LL now = C(s_1 + s1, i);
        if(i < s_1 - 1) f_2 += now;
        if(i == s_1 - 1) f_1 += now;
        if(i == s_1) f0 += now;
        if(i == s_1 + 1) f1 += now;
        if(i > s_1 + 1) f2 += now;
    }
    f_2 %= MOD; f_1 %= MOD; f0 %= MOD; f1 %= MOD; f2 %= MOD;
    return ;
}

int main() {
    search(string("LL_RR"));

    fac[0] = inv[0] = 1;
    rep(i, 1, maxm - 1) fac[i] = (LL)fac[i-1] * i % MOD, inv[i] = (LL)inv[i-1] * Pow(i, MOD - 2) % MOD;

    read(); int T = read();
    while(T--) {
        int n = read();
        rep(i, -3, 4) tot[i] = 0;
        rep(i, 1, n) scanf("%s", str), tot[val[string(str)]] += read();
        Creal(tot[-2], tot[-1], tot[1], tot[2]);
        Cfake(tot[-3], tot[3]);
        /*
        s0 and s1 are used to separate the imaginary(fake) part.
        s0: number of star is even
        s1: number of star is odd
        Only when star doesn‘t exist(or appears even number of times)
            situation abs(Up - Down) = 1 can be count in "L wins" or "R wins".
        */
        LL s0 = 1, s1 = 0, Lw = 0, Rw = 0, Fw = 0, Fl = 0;
        if(tot[4]) s0 = s1 = Pow(2, tot[4] - 1);
        // Real part determims who will win.
        Lw += t1 * Pow(2, tot[-3] + tot[4] + tot[3]) % MOD;
        Rw += t_1 * Pow(2, tot[-3] + tot[4] + tot[3]) % MOD;
        // When Real part is equal(total sum is 0)
        Lw += t0 * s0 % MOD * (f1 + f2) % MOD; // Up more, then L wins.
        Rw += t0 * s0 % MOD * (f_1 + f_2) % MOD; // Down more, then R wins.
        Fl += t0 * s0 % MOD * f0 % MOD; // First lose.(state 0, this state includes (or equally) nothing)
        Lw += t0 * s1 % MOD * f2 % MOD; // (Up - Down) more than one, then L wins.
        Rw += t0 * s1 % MOD * f_2 % MOD; // (Down - Up) more than one, then R wins.
        Fw += t0 * s1 % MOD * (f1 + f_1 + f0) % MOD; // First win.
        Lw %= MOD; Rw %= MOD; Fw %= MOD; Fl %= MOD;
        // At last, everybody multiply state 0 because we ignored it before.
        (Lw *= Pow(2, tot[0])) %= MOD;
        (Rw *= Pow(2, tot[0])) %= MOD;
        (Fw *= Pow(2, tot[0])) %= MOD;
        (Fl *= Pow(2, tot[0])) %= MOD;
        printf("%lld %lld %lld %lld\n", Lw, Rw, Fw, Fl);
    }

    return 0;
}
时间: 2024-11-07 10:42:20

[LOJ#2327]「清华集训 2017」福若格斯的相关文章

[LOJ#2325]「清华集训 2017」小Y和恐怖的奴隶主

[LOJ#2325]「清华集训 2017」小Y和恐怖的奴隶主 试题描述 "A fight? Count me in!" 要打架了,算我一个. "Everyone, get in here!" 所有人,都过来! 小Y是一个喜欢玩游戏的OIer.一天,她正在玩一款游戏,要打一个Boss. 虽然这个Boss有 \(10^{100}\) 点生命值,但它只带了一个随从--一个只有 \(m\) 点生命值的"恐怖的奴隶主". 这个"恐怖的奴隶主&qu

[LOJ#2328]「清华集训 2017」避难所

[LOJ#2328]「清华集训 2017」避难所 试题描述 "B君啊,你当年的伙伴都不在北京了,为什么你还在北京呢?" "大概是因为出了一些事故吧,否则这道题就不叫避难所了." "唔,那你之后会去哪呢?" "去一个没有冬天的地方." 对于一个正整数 \(n\),我们定义他在 \(b\) 进制下,各个位上的数的乘积为 \(p = F(n, b)\). 比如 \(F(3338, 10) = 216\). 考虑这样一个问题,已知 \

[LOJ#2331]「清华集训 2017」某位歌姬的故事

[LOJ#2331]「清华集训 2017」某位歌姬的故事 试题描述 IA是一名会唱歌的女孩子. IOI2018就要来了,IA决定给参赛选手们写一首歌,以表达美好的祝愿.这首歌一共有 \(n\) 个音符,第iii个音符的音高为 \(h_i\).IA的音域是 \(A\),她只能唱出 \(1\sim A\) 中的正整数音高.因此 \(1\le h_i\le A\). 在写歌之前,IA需要确定下这首歌的结构,于是她写下了 \(Q\) 条限制,其中第 \(i\) 条为:编号在 \(l_i\) 到 \(r_

Loj #6703 -「清华集训 2017」生成树计数

\[ \sum _{\sum v_i = n-2} \prod (a_i ^{v_i+1} * (v_i+1) ^ m /v_i!) *(\sum (v_i+1)^m) \] 将 \(\sum (v_i+1)^m\) 中的贡献分开算. 我们有两个生成函数. 第一个: \[ \sum _i a^{i+1} * (v_i+1)^m x^i / i! \] 第二个: \[ \sum _i a^{i+1} * (i+1)^{2m} x^i / i! \] 先将 \(\prod a\) 算到前面. 令:

@loj - [email&#160;protected] 「清华集训 2017」生成树计数

目录 @[email protected] @[email protected] @正文@ @补充@ @accepted [email protected] @[email protected] @[email protected] 在一个 s 个点的图中,存在 s - n 条边,使图中形成了 n 个连通块,第 i 个连通块中有 \(a_i\) 个点. 现在我们需要再连接 n - 1 条边,使该图变成一棵树.对一种连边方案,设原图中第 i 个连通块连出了 \(d_i\) 条边,那么这棵树 T 的

loj2322 「清华集训 2017」Hello world!

https://loj.ac/problem/2322 先吐槽一下,sb数据毁我青春败我前程. 首先,一个数开根开不了多少次. 当我们把它开到1的时候,我们以后就不需要开他了,我们可以利用并查集跳过他,这是套路. 但是这个每次走$k$步,让人很头痛. 于是乎……分块 首先,对于$k$比较大的情况,我们可以暴力跳.否则, 我们对于每个$k$建一棵树,第$i$棵树上$x$的父亲是原树上$x$的第$i$个祖先 然后树剖,用线段树或者树状数组维护一下就好了. 然后!!打开榜,rk1,啥子,并查集+暴力跳

LibreOJ #2325. 「清华集训 2017」小Y和恐怖的奴隶主(矩阵快速幂优化DP)

哇这题剧毒,卡了好久常数才过T_T 设$f(i,s)$为到第$i$轮攻击,怪物状态为$s$时对boss的期望伤害,$sum$为状态$s$所表示的怪物个数,得到朴素的DP方程$f(i,s)=\sum \frac{1}{sum+1}*(f(i+1,s')+[s==s'])$ 状态数只有$C_{8+3}^3=165$个,所以就可以矩乘优化啦.再加上一个用于转移的$1$,矩阵大小是$166*166$的,因为多组询问,所以可以先把$2$的所有次幂的矩阵都预处理出来. 然后会发现复杂度是$O(T*166^3

「清华集训 2017」无限之环

无限之WA https://www.luogu.org/problemnew/show/P4003 本题如果知道是网络流的话,其实建图不算特别神奇,但是比较麻烦. 数据范围过大,插头dp不能处理,而且是一个网格图,考虑网络流. 先看是不是二分图? 每个格子只会和相邻四个格子发生关系 所以,黑白染色正好. i+j为偶数左部点,i+j为奇数右部点 不漏水是什么? 每个管道的四个口都能和别的接好. S向左,右向T连口数的容量,费用为0的边 考虑怎么能把左右连在一起. 考虑要上下左右四个方向匹配,那么必

「清华集训 2017」某位歌姬的故事

题目链接 问题分析 吐槽一下这个预处理比DP还长的题-- 首先对限制从小到大排序,然后不难发现对于每一种大小限制都是独立的.离散后考虑\(F[i][j]\)表示以\(i\)结尾,上一个音高为限制大小的位置\(j\)的方案种数.不难发现对于一些右端点相同的限制,左端点最右的限制才有效.这样就可以\(n^2\)动规了. 由于要离散化,所以细节很多. 参考程序 程序没有显式的离散化,并且大量使用结构体,所以又慢又长. #include <bits/stdc++.h> #define LL long