In mathematics you don't understand things, you just get used to them.

分类 笔记 下的文章

Description

Link.

区间查 $x$ 的倍数并除掉,区间查和。

Solution

平衡树。

首先有个基本的想法就是按 $a_{i}$ 开平衡树,即对于每个 $a_{i}$ 都开一棵平衡树,共5e5棵,第 $i$ 棵平衡树维护的值是所有 $a_{i}$ 的倍数在原数组中的下标,用处后面讲。

注意到一个小性质,一个正整数 $A$ 最多被整除 $\log_{2}A$ 次,这个很好想,每次都至少减少一半。可以当成一个 trick 记下来。

整个区间的数最多被除 $\sum_{i=1}^{n}\log_{2}a_{i}$ 次,区间和的操作可以用树状数组操作实现,则整体的操作复杂度为 $\Theta(\sum_{i=1}^{n}\log_{2}a_{i}+\log_{2}a_{i})$。

开头提到了对于每个 $a_{i}$ 我们都开一棵平衡树,作用就体现在这里。因为如果要保证正确的时间复杂度,我们需要快速的找到需要执行操作的数。

这里我采用的是 FHQ-Treap。

我们可以用两次 split 操作在 $x$ 的平衡树中提取出当前的询问区间,由于我们是以下标为平衡树维护的权值,所以我们用按值分裂即可提取出区间。

然后我们就在提取出的子树中 DFS 遍历,然后暴力操作,把操作后依然是 $x$ 的倍数的数记录下来,操作完后用这个数组再建一棵临时树然后和之前 split 出来的子树一起合并回去。

操作之前记得预处理每个数的所有约数,这个简单,直接用 vector 即可。

#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <ctime>
#include <queue>

using namespace std;
typedef long long t_t;

const int Maxn = 1e5 + 5;
const int Maxa = 5e5 + 5;
int n, m, xxx, zip, tot, isa[Maxn], bin[Maxa], root[Maxa];
struct Treap
{
    int l, r, key, val;
} t[Maxa * 230];
struct Fenwick_Tree
{
    t_t bit[Maxn];
    
    void ins(int x, t_t v)
    {
        for (; x <= n; x += x & -x)     bit[x] += v;    
    }
    
    t_t sum(int x)
    {
        t_t ret = 0;
        for (; x; x -= x & -x)    ret += bit[x];
        return ret;
    }
} fwt;
vector < int > vec[Maxa];

int newnode(int val)
{
    t[++tot].val = val;
    t[tot].key = rand();
    return tot;
}

void split(int now, int val, int &x, int &y)
{
    if (now == 0)    x = y = 0;
    else
    {
        if (t[now].val <= val)
        {
            x = now;
            split(t[now].r, val, t[now].r, y);
        }
        else
        {
            y = now;
            split(t[now].l, val, x, t[now].l);
        }
    }
}

int merge(int x, int y)
{
    if (x == 0 || y == 0)    return x | y;
    if (t[x].key > t[y].key)
    {
        t[x].r = merge(t[x].r, y);
        return x;
    }
    else
    {
        t[y].l = merge(x, t[y].l);
        return y;
    }
}

int build(int l, int r)
{
    if (l > r)    return 0;
    int mid = (l + r) >> 1;
    int now = newnode(bin[mid]);
    t[now].l = build(l, mid - 1);
    t[now].r = build(mid + 1, r);
    return now;
}

void dfs(int now, int val)
{
    if (now == 0)    return ;
    dfs(t[now].l, val);
    if (isa[t[now].val] % val == 0)
    {
        fwt.ins(t[now].val, -isa[t[now].val]);
        isa[t[now].val] /= val;
        fwt.ins(t[now].val, isa[t[now].val]);
    }
    if (isa[t[now].val] % val == 0)    bin[++zip] = t[now].val;
    dfs(t[now].r, val);
}

int tx, ty, tp;
void change(int l, int r, int x)
{
    if (x == 1)     return ;
    split(root[x], r, tx, tp);
    split(tx, l - 1, tx, ty);
    zip = 0;
    dfs(ty, x);
    root[x] = merge(tx, merge(build(1, zip), tp));
}

signed main()
{
    srand((114514 - 1) / 3 - 4);
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; ++i)
    {
        scanf("%d", &isa[i]);
        fwt.ins(i, isa[i]);
        xxx = max(xxx, isa[i]);
    }
    for (int i = 1; i <= n; ++i)
    {
        for (int j = 1; j * j <= isa[i]; ++j)
        {
            if (isa[i] % j == 0)
            {
                vec[j].push_back(i);
                if (j * j != isa[i])    vec[isa[i] / j].push_back(i);
            }
        }
    }
    for (int i = 1; i <= xxx; ++i)
    {
        zip = 0;
        for (unsigned j = 0; j < vec[i].size(); ++j)    bin[++zip] = vec[i][j];
        root[i] = build(1, zip);
    }
    for (int i = 0; i < m; ++i)
    {
        int t, l, r, x;
        scanf("%d %d %d", &t, &l, &r);
        if (t == 1)
        {
            scanf("%d", &x);
            change(l, r, x);
        }
        else    printf("%lld\n", fwt.sum(r) - fwt.sum(l - 1));
    }
    return 0;
}

Description

Link.

无修改区间求逆序对。

Solution

首先有一个显然的 $\Theta(N\sqrt{N}\log_{2}N)$ 做法,由于过不了所以我就不废话。

其实有了 $\Theta(N\sqrt{N}\log_{2}N)$ 的过不去做法,我们就可以根据这个思路然后预处理解决问题。

我们需要处理的信息有:

  1. 散块的逆序对数量
  2. 以块为单位的区间逆序对数量

那么我们需要处理的数组就有以下几个:

  1. previous[i] 表示 $i$ 到 该块开头的逆序对数量。
  2. suffix[i] 同理。
  3. block[i][j] 表示前 $i$ 个块中 $\leq j$ 元素个数。
  4. intervals[i][j] 表示以块为单位的区间 $[i,j]$ 中的逆序对数量。

讲讲预处理方法。

  1. previous[i]suffix[i] 的处理方法都很显然,可以一直扫着然后FWT扫就行。
  2. block[i][j] 可以递推,递推式为 block[i][j]=block[i+1][j]+block[i][j-1]-block[i+1][j-1]+cont(i,j)。其中 cont(i,j) 表示计算对应块的逆序对数。
  3. intervals[i][j] 每次循环到块的开头继承上一个块的贡献即可。

计算贡献的方法很简单,归并即可。mrsrz讲得也挺清楚的,我这里就不再赘述,主要讲讲怎么卡常。

首先我们可以把主函数里的所有循环全部展开,经过实践参数传8的时候跑得比较快。

然后八聚氧先加上,luogu O2也开着。

再其次快读fread快输fwrite,这些都是卡常的标配。

然后就把能拿出来的结构体拿出来,实在不能就不管了。

然后去STL,pair vector能去就去。

然后long long开在正确的地方,不要无脑replace。

函数inline,循环register。虽然可能作用不大但是可以先加上。

然后调块长,经过无数次实践发现取150~170较为优秀。

然后加了过后发现就算rp再好也只有60pts。

然后谷歌搜索硫酸的化学式H₂SO₄,给评测机喂硫酸(idea来自SyadouHayami)。

然后本来交了5页都过不了,这下再交两次就过了。

// 省略八聚氧
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int Maxn = 1e5 + 5;
const int Maxm = 650;
const int each = 160;
int n, m, blocks, Lp[Maxn], Rp[Maxn], isa[Maxn], head[Maxn], tail[Maxn], sorted[Maxn], belong[Maxn], previous[Maxn], suffix[Maxn], block[Maxm][Maxn];
long long intervals[Maxm][Maxn];
struct Holy_Pair
{
    int first, second;

    bool operator < (const Holy_Pair& rhs) const
    {
        return first < rhs.first;
    }
} current[Maxn];
struct Fenwick_Tree
{
    int fwt[Maxn];
    
    inline void Modify(int x, int v)
    {
        for (; x + 5 <= Maxn; x += x & -x)    fwt[x] += v;
    }

    inline int Query(int x)
    {
        int ret = 0;
        for (; x; x ^= x & -x)    ret += fwt[x];
        return ret;
    }
} FWT;

#define io_e '\0'
#define io_s ' '
#define io_l '\n'
namespace Fast_IO
{
... // 省略快读
}  // namespace Fast_IO

using Fast_IO::read;
using Fast_IO::write;

inline Holy_Pair make_pair(int first, int second)
{
    Holy_Pair ret;
    ret.first = first;
    ret.second = second;
    return ret;
}

inline int Merge_Vct(int rhs[], int shr[], int szl, int szr)
{
    int itl = 1, itr = 1;
    int ret = 0, ctr = 1;
    while (itl <= szl && itr <= szr)
    {
        if (rhs[itl] < shr[itr])     ++itl, ++ctr;
        else
        {
            ret += szl - ctr + 1;
            ++itr;
        }
    }
    return ret + szr - szr;
}

inline int Merge_Idx(int st1, int st2, int sz1, int sz2)
{
    int ret = 0, id1 = st1 + 1, id2 = st2 + 1;
    sz1 += st1, sz2 += st2;
    while (id1 <= sz1 && id2 <= sz2)
    {
        if (sorted[id1] < sorted[id2])        ++id1;
        else
        {
            ret += sz1 - id1 + 1;
            ++id2;
        }
    }
    return ret;
}

inline void Behavior(int l, int r, long long &ans)
{
    int itl = 0, itr = 0;
    if (belong[l] == belong[r])
    {
        for (int i = head[belong[l]]; i <= tail[belong[r]]; ++i)
        {
            if (current[i].second >= l && current[i].second <= r)    Rp[++itr] = sorted[i];
            else if (current[i].second < l)        Lp[++itl] = sorted[i];
        }
        if (l == head[belong[l]])    ans = previous[r] - Merge_Vct(Lp, Rp, itl, itr);
        else     ans = previous[r] - previous[l - 1] - Merge_Vct(Lp, Rp, itl, itr);
    }
    else
    {
        ans = intervals[belong[l] + 1][belong[r] - 1] + previous[r] + suffix[l];
        for (int i = head[belong[l]]; i <= tail[belong[l]]; ++i)
        {
            if (current[i].second >= l)
            {
                Lp[++itl] = sorted[i];
                ans += block[belong[r] - 1][1] - block[belong[r] - 1][sorted[i]] - block[belong[l]][1] + block[belong[l]][sorted[i]];
            }
        }
        for (int i = head[belong[r]]; i <= tail[belong[r]]; ++i)
        {
            if (current[i].second <= r)
            {
                Rp[++itr] = sorted[i];
                ans += block[belong[r] - 1][sorted[i] + 1] - block[belong[l]][sorted[i] + 1];
            }
        }
        ans += Merge_Vct(Lp, Rp, itl, itr);
    }
    write(io_l, ans);
}

signed main()
{
    read(n, m), blocks = (n - 1) / each + 1;
    if (n <= 8)
    {
        for (int i = 1; i <= n; ++i)
        {
            read(isa[i]);
            current[i] = make_pair(isa[i], i);
        }
    }
    else
    {
        #pragma unroll 8
        for (int i = 1; i <= n; ++i)
        {
            read(isa[i]);
            current[i] = make_pair(isa[i], i);
        }
    }
    if (blocks <= 8)
    {
        for (int i = 1; i <= blocks; ++i)
        {
            head[i] = tail[i - 1] + 1;
            tail[i] = tail[i - 1] + each;
            if (i == blocks)     tail[i] = n;
        }
    }
    else
    {
        #pragma unroll 8
        for (int i = 1; i <= blocks; ++i)
        {
            head[i] = tail[i - 1] + 1;
            tail[i] = tail[i - 1] + each;
            if (i == blocks)     tail[i] = n;
        }
    }
    if (blocks <= 8)
    {
        for (int i = 1; i <= blocks; ++i)
        {
            memcpy(block[i], block[i - 1], sizeof(block[0]));
            sort(current + head[i], current + 1 + tail[i]);
            for (int j = head[i]; j <= tail[i]; ++j)
            {
                ++block[i][isa[j]];
                belong[j] = i;
                sorted[j] = current[j].first;
            }
            int satisfy = 0;
            for (int j = head[i]; j <= tail[i]; ++j)
            {
                FWT.Modify(isa[j], 1);
                satisfy += FWT.Query(n) - FWT.Query(isa[j]);
                previous[j] = satisfy;
            }
            intervals[i][i] = satisfy;
            for (int j = head[i]; j <= tail[i]; ++j)
            {
                suffix[j] = satisfy;
                FWT.Modify(isa[j], -1);
                satisfy -= FWT.Query(isa[j] - 1);
            }
        }
    }
    else
    {
        #pragma unroll 8
        for (int i = 1; i <= blocks; ++i)
        {
            memcpy(block[i], block[i - 1], sizeof(block[0]));
            sort(current + head[i], current + 1 + tail[i]);
            for (int j = head[i]; j <= tail[i]; ++j)
            {
                ++block[i][isa[j]];
                belong[j] = i;
                sorted[j] = current[j].first;
            }
            int satisfy = 0;
            for (int j = head[i]; j <= tail[i]; ++j)
            {
                FWT.Modify(isa[j], 1);
                satisfy += FWT.Query(n) - FWT.Query(isa[j]);
                previous[j] = satisfy;
            }
            intervals[i][i] = satisfy;
            for (int j = head[i]; j <= tail[i]; ++j)
            {
                suffix[j] = satisfy;
                FWT.Modify(isa[j], -1);
                satisfy -= FWT.Query(isa[j] - 1);
            }
        }
    }
    if (blocks <= 8)
    {
        for (int dis = 1; dis <= blocks; ++dis)
        {
            for (int i = n - 1; i; --i)        block[dis][i] += block[dis][i + 1];
            for (int l = 1, r = dis + 1; r <= blocks + 1; ++l, ++r)
                intervals[l][r] = intervals[l + 1][r] + intervals[l][r - 1] - intervals[l + 1][r - 1] +
                                    Merge_Idx(head[l] - 1, head[r] - 1, tail[l] - head[l] + 1, tail[r] - head[r] + 1);
        }
    }
    else
    {
        #pragma unroll 8
        for (int dis = 1; dis <= blocks; ++dis)
        {
            for (int i = n - 1; i; --i)        block[dis][i] += block[dis][i + 1];
            for (int l = 1, r = dis + 1; r <= blocks + 1; ++l, ++r)
                intervals[l][r] = intervals[l + 1][r] + intervals[l][r - 1] - intervals[l + 1][r - 1] +
                                    Merge_Idx(head[l] - 1, head[r] - 1, tail[l] - head[l] + 1, tail[r] - head[r] + 1);
        }
    }

    if (m <= 8)
    {
        long long lastans = 0;
        for (int i = 0; i < m; ++i)
        {
            long long l, r;
            read(l, r);
            l ^= lastans;
            r ^= lastans;
            Behavior(l, r, lastans);
        }
    }
    else
    {
        long long lastans = 0;
        #pragma unroll 8
        for (int i = 0; i < m; ++i)
        {
            long long l, r;
            read(l, r);
            l ^= lastans;
            r ^= lastans;
            Behavior(l, r, lastans);
        }
    }
    return 0;
}

Description

Link.

无修支持查询:查询一个区间 $[l,r]$ 中所有子序列分别去重后的和 $\bmod\ p$

Solution

这是数据结构一百题的第50题(一半了哦)的纪念题解。

无修改操作,基本确定大方向莫队。

考虑查询的问题,我们可以转化一下。即求区间内每个数出现的次数。

一个区间 $[l,r]$ 的子序列数量为:

$$\sum_{i=0}^{r-l+1}C^{i}_{r-l+1}=2^{r-l+1}$$

比如一个数在区间 $[l,r]$ 出现了 $k$ 次,那么一共有 $2^{r-l+1-k}$ 个子序列不包含这个数。这个很好理解,从组合数的意义可知。那么就有 $2^{r-l+1}-2^{r-l+1-k}$ 个子序列包含了这个数。

那么我们就可以用莫队维护区间中出现了 $k$ 次的所有数的和,然后乘上一个 $2^{r-l+1}-2^{r-l+1-k}$ 就是它的贡献了。

问题又来了:每次询问的模数是不确定的,我们需要每次都需要 $\Theta(n)$ 处理一遍2的幂。

有没有什么方法能把处理这个东西的复杂度降到 $\Theta(\sqrt{n})$ 或以下呢?

对此SyadouHayami表示我们可以打个高精。

方法是有的。

我们可以每次询问都处理出 $2^{1},2^{2},\cdots,2^{\sqrt{n}}$ ,以及 $2^{2\sqrt{n}},2^{3\sqrt{n}},\cdots,2^{n}$,都只需要 $\Theta(\sqrt{n})$。当然,都是在模 $p$ 的意义下的。我们分别记为pow1pow2

那么 $2^{x}\operatorname{mod}p=(pow1_{x\operatorname{mod}\sqrt{n}}\times pow2_{\lfloor x\div\sqrt{n}\rfloor})\operatorname{mod}p$。

于是就解决问题了。

我的代码遇到了一下两个玄学问题,贴出来给同样情况的人看看:

  1. 链表部分的prevnext如果放在结构体里会T。
  2. pow1,pow2,sum,cnt几个数组的定义如果放在最开头和isa以及ans两个数组一起会RE。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>

using namespace std;

const int Maxn = 1e5 + 10;
const int Size = 320;
int n, m, isa[ Maxn ], ans[ Maxn ];
struct Query_Node
{
    int l, r, p, id, pos;
} Q[ Maxn ];
struct Linked_List
{
    int tot, prev[ Maxn ], next[ Maxn ];
    Linked_List( ) { tot = 0; }

    void insert( int x )
    {
        next[ tot ] = x;
        prev[ x ] = tot;
        tot = x;
    }

    void erase( int x )
    {
        if( tot == x )    tot = prev[ x ];
        else
        {
            next[ prev[ x ] ] = next[ x ];
            prev[ next[ x ] ] = prev[ x ];
        }
        prev[ x ] = next[ x ] = 0;
    }
} llt;

bool cmp( Query_Node rhs, Query_Node shr )
{
    if( rhs.pos != shr.pos )    return rhs.l < shr.l;
    else    return rhs.r < shr.r;
}

int pow1[ Maxn ], pow2[ Maxn ];
void Pare_Two( int p )
{
    pow1[ 0 ] = pow2[ 0 ] = 1;
    for( int i = 1; i <= Size; ++ i )     pow1[ i ] = 1ll * 2 * pow1[ i - 1 ] % p;
    for( int i = 1; i <= Size; ++ i )    pow2[ i ] = 1ll * pow1[ Size ] * pow2[ i - 1 ] % p;
}

int Get_Two( int x, int p )
{
    return 1ll * pow1[ x % Size ] * pow2[ x / Size ] % p;
}

int sum[ Maxn ], cnt[ Maxn ];
void Make_Cont( int x, int f )
{
    int pos = isa[ x ];
    sum[ cnt[ pos ] ] -= pos;
    if ( ! sum[ cnt[ pos ] ] ) llt.erase( cnt[ pos ] );
    if( f == 1 ) ++cnt[ pos ];
    else --cnt[ pos ];
    if ( ! sum[ cnt[ pos ] ] ) llt.insert( cnt[ pos ] );
    sum[ cnt[ pos ] ] += pos;
}

void Contribute( )
{
    int l = 1, r = 0;
    for( int i = 1; i <= m; ++ i )
    {
        Pare_Two( Q[ i ].p );
        while( l > Q[ i ].l ) Make_Cont( --l, 1 );
        while( l < Q[ i ].l ) Make_Cont( l++, 0 );
        while( r > Q[ i ].r ) Make_Cont( r--, 0 );
        while( r < Q[ i ].r ) Make_Cont( ++r, 1 );
        for( int s = llt.next[ 0 ]; s; s = llt.next[ s ] )
        {
            int val = 1ll * sum[ s ] * ( Get_Two( r - l + 1, Q[ i ].p ) - Get_Two( r - l + 1 - s, Q[ i ].p ) + Q[ i ].p ) % Q[ i ].p;
            ans[ Q[ i ].id ] += val;
            ans[ Q[ i ].id ] %= Q[ i ].p;
        }
    }
}

signed main( )
{
    scanf( "%d %d", &n, &m );
    for( int i = 1; i <= n; ++ i )    scanf( "%d", &isa[ i ] );
    for( int i = 1; i <= m; ++ i )
    {
        int l, r, p;
        scanf( "%d %d %d", &l, &r, &p );
        Q[ i ].l = l, Q[ i ].r = r;
        Q[ i ].p = p, Q[ i ].id = i;
        Q[ i ].pos = l / Size;
    }
    sort( Q + 1, Q + 1 + m, cmp );
    Contribute( );
    for( int i = 1; i <= m; ++ i )
        printf( "%d\n", ans[ i ] );
    return 0;
}

Description

(Link)[https://www.luogu.com.cn/problem/P4688].

每次询问三个区间,把三个区间中同时出现的数一个一个删掉,问最后三个区间剩下的数的个数和,询问独立。

Solution

读完题,我们可以发现每轮询问的答案是这个东西:

$$ \sum_{i=1}^{3}(r_{i}-l_{i}+1)-P\times 3 $$

其中,$P$ 表示三个区间里面的公共颜色数量。

前面那个 $\sum$ 里面的东西很好维护,我们主要来讲后面一部分的维护方法。

首先初始序列的 $a_{i}$ 是一定要离散化一遍的。

那么我们如何表示出出现的最少次数呢?

如果这是一个二进制串的话,我们可以发现这就是一个 $\operatorname{bit\_and}$ 的操作。

对于每个询问,我们可以把三个区间分开处理再合并。直接维护复杂度过高,用 bitset 优化。由于这是个二进制序列,所以离散化的时候不能去重,否则答案就会偏大。

直接操作容易 MLE,这里介绍一个小trick。我们可以把询问分批处理,这样就行了。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <bitset>
#include <cmath>

using namespace std;
typedef long long LL;
typedef unsigned long long ULL;

const int N = 1e5 + 5;
const int F = 23333 + 5;
int n, m, block, origin[N], appear[N];
int cnt[N], ans[N], vis[25005];
int l1[N], l2[N], l3[N];
int r1[N], r2[N], r3[N];
bitset < N > S[F];
vector < int > disc;
struct AskSet {
    int l, r;
    int id, p;
} Q[N];

int GetID(int x) {
    return lower_bound(disc.begin(), disc.end(), x) - disc.begin() + 1;
}

bool SortWith(const AskSet& x, const AskSet& y) {
    return (x.p < y.p) || (x.p == y.p && x.r < y.r);
}

void MakeCont(int pos, int flags, bitset < N >& bit) {
    pos = origin[pos];
    if (flags > 0) bit[pos + cnt[pos]] = 1;
    else bit[pos + cnt[pos] - 1] = 0;
    cnt[pos] += flags;
}

void Contribute(int fr, int ba) {
    memset(cnt, 0, sizeof cnt);
    memset(vis, 0, sizeof vis);
    bitset < N > bit; bit.reset();
    int l = 1, r = 0, subs = 0;
    for (int i = fr; i <= ba; ++i) {
        Q[++subs] = {l1[i], r1[i], i, appear[l1[i]]};
        Q[++subs] = {l2[i], r2[i], i, appear[l2[i]]};
        Q[++subs] = {l3[i], r3[i], i, appear[l3[i]]};
        ans[i] += (r3[i] - l3[i]) + (r2[i] - l2[i]) + (r1[i] - l1[i]) + 3;
    }
    sort(Q + 1, Q + 1 + subs, SortWith);
    for (int i = 1; i <= subs; ++i) {
        while (r < Q[i].r) MakeCont(++r, 1, bit);
        while (l > Q[i].l) MakeCont(--l, 1, bit);
        while (r > Q[i].r) MakeCont(r--, -1, bit);
        while (l < Q[i].l) MakeCont(l++, -1, bit);
        if (!vis[Q[i].id - fr + 1]) {
            vis[Q[i].id - fr + 1] = 1;
            S[Q[i].id - fr + 1] = bit;
        } else {
            S[Q[i].id - fr + 1] &= bit;
        }
    }
    for (int i = fr; i <= ba; ++i)
        ans[i] -= S[i - fr + 1].count() * 3;
}

signed main() {
    scanf("%d %d", &n, &m);
    block = sqrt(n);
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &origin[i]);
        appear[i] = (i - 1) / block + 1;
        disc.push_back(origin[i]);
    }
    sort(disc.begin(), disc.end());
    for (int i = 1; i <= n; ++i)
        origin[i] = GetID(origin[i]);
    for (int i = 1; i <= m; ++i) {
        scanf("%d %d", &l1[i], &r1[i]);
        scanf("%d %d", &l2[i], &r2[i]);
        scanf("%d %d", &l3[i], &r3[i]);
    }
    for (int i = 1; i <= m; i += 23333) Contribute(i, min(m, i + 23332));
    for (int i = 1; i <= m; ++i) printf("%d\n", ans[i]);
    return 0;
}

Description

Link.

这道题 的加强版。

Solution

题解里面大多数都是概率 DP,或者是期望 DP 然后是逆推。甚至不给 DP 的转移式。机房 yyds Reanap 发了一篇逆推的题解,那我就来补一篇正推的期望 DP 的填表法做法。

首先这道题看上去好像可以状压的样子,我们可以设 $f_{i,S}$ 表示当前打了 $i$ 次,敌方的情况是 $S$ 的期望。

不过仔细想一下发现我们只需要知道各种血量的奴隶主有多少即可。

于是我们重新设计 DP 的状态:$f_{s,i,j,k}$ 表示目前打了 $s$ 次,敌方分别有 $i$、$j$、$k$ 个 1hp、2hp、3hp 的奴隶主。

首先我们令 $T=[i+j+k<K]$

那么我们的方程就是:

$$ f_{s,i,j,k}=\begin{cases} f_{s-1,i-1,j,k}+\frac{i}{i+j+k+1},M=1\land i\neq0 \\ f_{s-1,i-1,j,k}+\frac{i}{i+j+k+1},M=2\land i\neq0 \\ f_{s-1,i+1,j-1+T,k}+\frac{j}{i+j+k+1},M=2\land j\neq0 \\ f_{s-1,i-1,j,k}+\frac{i}{i+j+k+1},M=3\land i\neq0 \\ f_{s-1,i+1,j-1,k+T}+\frac{j}{i+j+k+1},M=3\land j\neq0 \\ f_{s-1,i,j+1,k-1+T}+\frac{k}{i+j+k+1},M=3\land k\neq0 \end{cases} $$

这个方程挺好理解的,基本就等于照题意模拟。

然后我们发现转移式中的系数部分和 $f$ 数组没有关系,所以我们可以用矩阵来加速这个东西。

数一数状态数,直接加速直接 T 飞。

有一个矩阵加速常用的 trick,预处理矩阵 2 的幂。

然后取模卡卡常即可。

(代码不保证稳定能过)

#include <cstdio>
#define mod ( 998244353 )

using namespace std;
typedef long long LL;

char buf[1 << 21], *p1 = buf, *p2 = buf;
#define getchar( ) ( p1 == p2 && ( p2 = ( p1 = buf ) + fread( buf, 1, 1 << 21, stdin ), p1 == p2 ) ? EOF : *p1 ++ )

template<typename _T>
void read( _T &x )
{
    x = 0;
    char c = getchar( );
    _T f = 1;
    while( c < '0' || c > '9' )
    {
        if( c == '-' )    f = -1;
        c = getchar( );
    }
    while( c >= '0' && c <= '9' )    x = ( x << 3 ) + ( x << 1 ) + ( c ^ '0' ), c = getchar( );
    x *= f;
}

template<typename _T, typename... Args>
void read( _T &t, Args&... args ) { read( t ), read( args... ); }

template<typename _T>
void write( _T x )
{
    if( x < 0 )    putchar( '-' ), x = -x;
    if( x > 9 )    write( x / 10 );
    putchar( x % 10 + '0' );
}

template<typename _T>
void Add( _T &x, _T y )
{
    if( y >= mod )  y %= mod;
    x += y;
    if( x >= mod )  x -= mod;
}

template<typename _T>
_T square( _T x ) { return x * x; }

const int Maxn = 10 + 5, Maxk = 170 + 5;
int T, M, K, S, Unite[Maxn][Maxn][Maxn];
LL tmp[Maxk], Ans[Maxk], Inv[Maxn];
struct Matrix
{
    LL mat[Maxk][Maxk];

    friend Matrix operator * ( const Matrix &one, const Matrix &another )
    {
        Matrix res;
        for( int i = 0; i <= S + 1; ++ i )
        {
            for( int j = 0; j <= S + 1; ++ j )
            {
                res.mat[i][j] = 0;
                for( int k = 0; k <= S + 1; ++ k )    Add( res.mat[i][j], one.mat[i][k] * another.mat[k][j] );
            }
        }
        return res;
    }
} dp[Maxk];

template<typename _T>
_T qkpow( _T base, _T times )
{
    _T res = 1;
    while( times )
    {
        if( times & 1 )    res = ( LL )res * base % mod;
        base = ( LL )base * base % mod;
        times >>= 1;
    }
    return res;
}

void progressInversions( ) { for( int i = 0; i <= 10; ++ i )    Inv[i] = qkpow( i, mod - 2 ); }
signed main( )
{
    progressInversions( );
    read( T, M, K );
    for( int i = 0; i <= K; ++ i )
    {
        int UpI;
        if( M > 1 )    UpI = K - i;
        else    UpI = 0;
        for( int j = 0; j <= UpI; ++ j )
        {
            int UpJ;
            if( M > 2 )    UpJ = K - i - j;
            else    UpJ = 0;
            for( int k = 0; k <= UpJ; ++ k )    Unite[i][j][k] = ++ S;
        }
    }
    for( int i = 0; i <= K; ++ i )
    {
        int UpI;
        if( M > 1 )    UpI = K - i;
        else    UpI = 0;
        for( int j = 0; j <= UpI; ++ j )
        {
            int UpJ;
            if( M > 2 )    UpJ = K - i - j;
            else    UpJ = 0;
            for( int k = 0; k <= UpJ; ++ k )
            {
                int Add;
                if( i + j + k < K )    Add = 1;
                else    Add = 0;
                if( M == 1 && i )    dp[0].mat[Unite[i][j][k]][Unite[i - 1][j][k]] = ( LL )i * Inv[i + j + k + 1] % mod;
                else if( M == 2 )
                {
                    if( i )    dp[0].mat[Unite[i][j][k]][Unite[i - 1][j][k]] = ( LL )i * Inv[i + j + k + 1] % mod;
                    if( j )    dp[0].mat[Unite[i][j][k]][Unite[i + 1][j - 1 + Add][k]] = ( LL )j * Inv[i + j + k + 1] % mod;
                }
                else if( M == 3 )
                {
                    if( i )    dp[0].mat[Unite[i][j][k]][Unite[i - 1][j][k]] = ( LL )i * Inv[i + j + k + 1] % mod;
                    if( j )    dp[0].mat[Unite[i][j][k]][Unite[i + 1][j - 1][k + Add]] = ( LL )j * Inv[i + j + k + 1] % mod;
                    if( k )    dp[0].mat[Unite[i][j][k]][Unite[i][j + 1][k - 1 + Add]] = ( LL )k * Inv[i + j + k + 1] % mod;
                }
                dp[0].mat[Unite[i][j][k]][Unite[i][j][k]] = dp[0].mat[Unite[i][j][k]][S + 1] = Inv[i + j + k + 1];
            }
        }
    }
    dp[0].mat[S + 1][S + 1] = 1;
    for( int i = 1; i <= 60; ++ i )    dp[i] = square( dp[i - 1] );
    while( T -- > 0 )
    {
        LL N;
        read( N );
        for( int i = 0; i <= S + 1; ++ i )  Ans[i] = 0;
        if( M == 1 )    Ans[Unite[1][0][0]] = 1;
        else if( M == 2 )    Ans[Unite[0][1][0]] = 1;
        else    Ans[Unite[0][0][1]] = 1;
        for( int i = 0; i <= 60; ++ i )
        {
            if( ( N >> i ) & 1 )
            {
                for( int j = 0; j <= S + 1; ++ j )
                {
                    tmp[j] = 0;
                    for( int k = 0; k <= S + 1; ++ k )    Add( tmp[j], Ans[k] * dp[i].mat[k][j] );
                }
                for( int j = 0; j <= S + 1; ++ j )    Ans[j] = tmp[j];
            }
        }
        write( Ans[S + 1] ), putchar( '\n' );
    }
    return 0;
}