CF Round 542 Div1.

Posted kong-ruo

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CF Round 542 Div1.相关的知识,希望对你有一定的参考价值。

D. isolation

给一个数组 a,把数组分成若干不相交的子段,使得每段“只出现一次的数”不超过 k 个

求有多少种分法,膜 998244353

$n leq 10^5$

 

sol:

考虑 dp,令 $f(l,r)=[l,r]中只出现一次的数的数量$,假设可以很快的算出 $f(l,r)$ ,令 $dp(i)=前i个的分法$ ,则 $dp(0)=1,dp(n) = sumlimits_{i=0}^{n} dp(i) imes [f(i+1,n) leq k]$

现在只要快速算出 $f(l,r)$ 并想办法加速转移即可

然后是一系列神仙操作:

首先,枚举 $r$,尝试构造一个数组 $b[l,r]$ 使得对于枚举的 $r$,$f(l,r) = sumlimits_{i=l}^r b[i]$,

因为我们要枚举 $r$ ,所以只需考虑如何快速使 $b_x[]$ 变成 $b_{x+1}[]$ 即可($b_x$ 表示当 $r=x$ 时的 $b[]$)

一开始,$b_0[0]=0$,为方便描述,记 $pre(x)$ 为数字 $x$ 上一次在 $a$ 数组中出现的位置,保证存在

对于给定的 $a_{x+1}$

如果 $a_{x+1}$ 在之前至少出现过 $2$ 次,把 $b[pre(pre(x+1))]$ 设为 $0$

如果 $a_{x+1}$ 在之前出现过 $1$ 次,把 $b[pre(x+1)]$ 设为 $-1$

最后不管如何,把 $b[x+1]$ 设为 $1$

这样就能保证:出现两次或以上的数对 sumb 的贡献为 0,出现一次的数对 sumb 的贡献为 1

b 数组可以 $O(n)$ 递推出来,考虑怎么优化转移

考虑分块(这就是我的知识盲区了)

每块 [l,r] 维护:

1. $f(l,r)$ (也就是 sumb)

2. 对于每一个 $i in [-sqrt{n},sqrt{n}]$,维护对于每个 $f(x,r) leq i$ 的 $x$ ,维护 $dp(x-1)$ 的和

对于 2. ,因为 $i$ 是递增的,我们可以对 $i$ 维护一个前缀和

转移的时候在本块以及前面的块里查,本块查的是 $[0,k]$ ,往前查就把右端点每次减这一块的 sumb 就可以了

这样递推到每一个 $r$ 的时候,单块修改 $O(sqrt{n})$ ,状态转移最多涉及到 $O(sqrt{n})$ 个块

一路递推到 $n$ 就可以了

技术图片
#include<bits/stdc++.h>
#define LL long long
#define rep(i, s, t) for(register int i = s, i##end = t; i <= i##end; ++i)
#define dwn(i, s, t) for(register int i = s, i##end = t; i >= i##end; --i)
using namespace std;
inline int read()
{
    int x = 0, f = 1; char ch = getchar();
    for(;!isdigit(ch);ch=getchar())if(ch == -)f = -f;
    for(;isdigit(ch);ch=getchar())x = 10 * x + ch - 0;
    return x * f;
}
const int mod = 998244353,maxn = 1e5 + 10;
int n,k;
int a[maxn], b[maxn], nx[maxn], cur[maxn], f[maxn], bl[maxn];
inline void inc(int &x, int y) {x += y; if(x >= mod) x -= mod;}
struct block
{
    int l, r, sum, sumf[700], sz;
    void calsum()
    {
        memset(sumf, 0, sizeof(sumf));
        int cur = 0;
        dwn(i, r, l)
        {
            cur += b[i];
            inc(sumf[sz + cur], f[i - 1]);
        }
        rep(i, 1, sz + sz) inc(sumf[i], sumf[i - 1]);
    }
    int query(int pos)
    {
        if(pos + sz < 0)return 0;
        return sumf[min(pos + sz, sz + sz)];
    }
}bs[350];

inline void update(int pos, int val)
{
    f[pos] = val;
    bs[bl[pos + 1]].calsum();
}

int query(int x)
{
    int ans = bs[bl[x]].query(k), cursum = bs[bl[x]].sum;
    dwn(i, bl[x]-1, 1)
    {
        inc(ans, bs[i].query(k - cursum));
        cursum += bs[i].sum;
    }
    return ans;
}

int main()
{
    n = read(), k = read();
    rep(i, 1, n) a[i] = read();
    rep(i, 1, n) nx[i] = cur[a[i]], cur[a[i]] = i;
    f[0] = 1; int kk = sqrt(n);
    rep(i, 1, n) bl[i] = (i - 1) / kk + 1;
    rep(i, 1, bl[n])
    {
        bs[i].r = min(i * kk, n);
        bs[i].l = bs[i-1].r + 1;
        bs[i].sz = bs[i].r - bs[i].l + 1;
    }bs[1].calsum();
    rep(i, 1, n)
    {
        if(nx[nx[i]])
        {
            int pos = nx[nx[i]]; b[pos]++;
            bs[bl[pos]].sum++; bs[bl[pos]].calsum();
        }
        if(nx[i])
        {
            int pos = nx[i]; b[pos] -= 2;
            bs[bl[pos]].sum -= 2; bs[bl[pos]].calsum();
        }
        b[i]++; bs[bl[i]].sum++; bs[bl[i]].calsum();
        update(i, (f[i] = query(i)));
    }
    cout << f[n] << endl;
}
View Code

 

以上是关于CF Round 542 Div1.的主要内容,如果未能解决你的问题,请参考以下文章

CF Round 594

AIM Tech Round 5 1028cf(A-E)

代码源 Div1 - 107#452. 序列操作(思维)CF1198B

CF542DSuperhero's Job 暴力

Codeforces Round #542 (Div. 2)

代码源 Div1 - 107#452. 序列操作(思维)CF1198B