Break Sequence
题意
给定一个长度为
n
n
n 的数组
a
a
a 和一个大小为
m
m
m 的集合
S
S
S
计算有多少种方式将
a
a
a 划分成若干个子段
[
1
,
p
1
]
,
[
p
1
+
1
,
p
2
]
,
.
.
.
.
.
,
[
p
k
−
1
+
1
,
n
]
[1, p_1], [p_1 + 1, p_2], ....., [p_{k - 1} + 1, n]
[1,p1],[p1+1,p2],.....,[pk−1+1,n],满足每个子段内,每种数字的出现次数都不等于
S
S
S 中的元素
1
≤
n
≤
2
×
1
0
5
1 \leq n \leq 2 \times 10^5
1≤n≤2×105
0
≤
m
≤
10
0 \leq m \leq 10
0≤m≤10
1
≤
a
i
≤
n
1 \leq a_i \leq n
1≤ai≤n
思路
定义
d
p
i
dp_i
dpi 为
[
1
,
i
]
[1, i]
[1,i] 的最后一个子段末尾在
i
i
i 的合法方案数,可以直接
O
(
n
2
)
O(n^2)
O(n2) 转移
由于
m
m
m 很小,我们进一步观察发现:在每个
i
i
i,对于当前的
a
i
a_i
ai,其出现次数在
S
S
S 中的情况对应的左端点(前缀和)一定不超过
O
(
∣
S
∣
)
O(|S|)
O(∣S∣) 个区间
于是我们可以每次扫到 a i a_i ai 的时候,在线更新这些非法区间,不允许从这些区间转移,注意如果当前 a i a_i ai 在前面已经出现过,我们要先消除前面打上的标记,因为我们当前考虑的是结尾在 i i i
区间标记我们可以用懒标线段树,最后区间查询 t a g tag tag 最少( min \min min 为 0 0 0)的区间,将这些区间的 d p dp dp 值求和返回即可
线段树的信息包含两个:
m
n
mn
mn(最小值) 和
s
u
m
sum
sum(这个区间的最小
t
a
g
tag
tag 为
m
n
mn
mn 的
d
p
dp
dp 值之和)
叶子节点的
s
u
m
sum
sum 即为这个位置的
d
p
dp
dp 值,向上合并左右儿子或查询的时候,比较一下两者的
m
n
mn
mn 大小,
s
u
m
sum
sum 值即为
m
n
mn
mn 最小的值之和
时间复杂度: O ( n m log n ) O(nm\log n) O(nmlogn)
#include<bits/stdc++.h>
#define fore(i,l,r) for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n'
#define ull unsigned long long
#define ALL(v) v.begin(), v.end()
#define Debug(x, ed) std::cerr << #x << " = " << x << ed;
const int INF=0x3f3f3f3f;
const long long INFLL=1e18;
typedef long long ll;
template<class T>
constexpr T power(T a, ll b){
T res = 1;
while(b){
if(b&1) res = res * a;
a = a * a;
b >>= 1;
}
return res;
}
constexpr ll mul(ll a,ll b,ll mod){ //快速乘,避免两个long long相乘取模溢出
ll res = a * b - ll(1.L * a * b / mod) * mod;
res %= mod;
if(res < 0) res += mod; //误差
return res;
}
template<ll P>
struct MLL{
ll x;
constexpr MLL() = default;
constexpr MLL(ll x) : x(norm(x % getMod())) {}
static ll Mod;
constexpr static ll getMod(){
if(P > 0) return P;
return Mod;
}
constexpr static void setMod(int _Mod){
Mod = _Mod;
}
constexpr ll norm(ll x) const{
if(x < 0){
x += getMod();
}
if(x >= getMod()){
x -= getMod();
}
return x;
}
constexpr ll val() const{
return x;
}
explicit constexpr operator ll() const{
return x; //将结构体显示转换为ll类型: ll res = static_cast<ll>(OBJ)
}
constexpr MLL operator -() const{ //负号,等价于加上Mod
MLL res;
res.x = norm(getMod() - x);
return res;
}
constexpr MLL inv() const{
assert(x != 0);
return power(*this, getMod() - 2); //用费马小定理求逆
}
constexpr MLL& operator *= (MLL rhs) & { //& 表示“this”指针不能指向一个临时对象或const对象
x = mul(x, rhs.x, getMod()); //该函数只能被一个左值调用
return *this;
}
constexpr MLL& operator += (MLL rhs) & {
x = norm(x + rhs.x);
return *this;
}
constexpr MLL& operator -= (MLL rhs) & {
x = norm(x - rhs.x);
return *this;
}
constexpr MLL& operator /= (MLL rhs) & {
return *this *= rhs.inv();
}
friend constexpr MLL operator * (MLL lhs, MLL rhs){
MLL res = lhs;
res *= rhs;
return res;
}
friend constexpr MLL operator + (MLL lhs, MLL rhs){
MLL res = lhs;
res += rhs;
return res;
}
friend constexpr MLL operator - (MLL lhs, MLL rhs){
MLL res = lhs;
res -= rhs;
return res;
}
friend constexpr MLL operator / (MLL lhs, MLL rhs){
MLL res = lhs;
res /= rhs;
return res;
}
friend constexpr std::istream& operator >> (std::istream& is, MLL& a){
ll v;
is >> v;
a = MLL(v);
return is;
}
friend constexpr std::ostream& operator << (std::ostream& os, MLL& a){
return os << a.val();
}
friend constexpr bool operator == (MLL lhs, MLL rhs){
return lhs.val() == rhs.val();
}
friend constexpr bool operator != (MLL lhs, MLL rhs){
return lhs.val() != rhs.val();
}
};
const ll mod = 998244353;
using Z = MLL<mod>;
template<>
ll MLL<0ll>::Mod = 998244353;
template<class Info, class Tag>
struct LazySegmentTree {
const int n;
std::vector<Info> info;
std::vector<Tag> tag;
LazySegmentTree(int n) : n(n), info(4 << std::__lg(n)), tag(4 << std::__lg(n)) {}
LazySegmentTree(std::vector<Info> init) : LazySegmentTree(init.size()) {
std::function<void(int, int, int)> build = [&](int p, int l, int r) {
if (l == r) {
info[p].x = init[l - 1].x; //传入的数组0indexed
info[p].len = 1;
return;
}
int m = l + r >> 1;
build(p << 1, l, m);
build(p << 1 | 1, m + 1, r);
pull(p);
};
build(1, 1, n);
}
void pull(int p) {
info[p] = info[p << 1] + info[p << 1 | 1];
}
void apply(int p, const Tag &v) {
info[p].apply(v);
tag[p].apply(v);
}
void push(int p) {
apply(p << 1, tag[p]);
apply(p << 1 | 1, tag[p]);
tag[p] = Tag();
}
Info query(int p, int l, int r, int ql, int qr) {
if(qr < l || r < ql) return Info();
if(ql <= l && r <= qr) return info[p];
int m = l + r >> 1;
push(p);
return query(p << 1, l, m, ql, qr) + query(p << 1 | 1, m + 1, r, ql, qr);
}
void update(int p, int l, int r, int ql, int qr, const Tag &v) {
if(qr < l || r < ql) return;
if(ql <= l && r <= qr){
apply(p, v);
return;
}
int m = l + r >> 1;
push(p);
if(ql <= m) update(p << 1, l, m, ql, qr, v);
if(qr > m) update(p << 1 | 1, m + 1, r, ql, qr, v);
pull(p);
}
void update(int p, int l, int r, int qp, const Info &v) {
if(l == r){
info[p] = v;
return;
}
int m = l + r >> 1;
push(p);
if(qp <= m) update(p << 1, l, m, qp, v);
else update(p << 1 | 1, m + 1, r, qp, v);
pull(p);
}
};
struct Tag {
int add = 0;
Tag(int _add = 0) : add(_add) {}
void apply(Tag t) {
add += t.add;
}
};
struct Info {
int mn = 0;
Z w = 0;
Info(int _mn = 0, Z _w = 0) : mn(_mn), w(_w) {}
void apply(Tag t) {
mn += t.add;
}
};
Info operator+(Info a, Info b) {
Info c;
c.mn = std::min(a.mn, b.mn);
if(a.mn == b.mn) c.w = a.w + b.w;
else if(a.mn < b.mn) c.w = a.w;
else c.w = b.w;
return c;
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
int n, m;
std::cin >> n >> m;
std::vector<int> a(n + 1);
std::vector<std::vector<int>> pos(n + 1);
std::vector<int> s(m);
fore(i, 1, n + 1) std::cin >> a[i];
fore(i, 0, m) std::cin >> s[i];
std::sort(ALL(s));
LazySegmentTree<Info, Tag> seg(n);
std::vector<Z> dp(n + 1 , 0);
dp[0] = 1;
const int D = 1;
seg.update(1, 1, n, 0 + D, Info(0, 1));
auto work = [&](int val, int delta){
int sz = pos[val].size();
for(auto k : s){
if(k > sz) return;
int l = (k == sz ? 0 : pos[val][sz - k - 1]), r = pos[val][sz - k] - 1;
seg.update(1, 1, n, l + D, r + D, Tag(delta));
}
};
fore(i, 1, n + 1){
work(a[i], -1); //消除之前的tag
pos[a[i]].push_back(i);
work(a[i], 1);
auto [mn, w] = seg.query(1, 1, n, 0 + D, i - 1 + D);
if(!mn) dp[i] = w;
if(i < n) seg.update(1, 1, n, i + D, Info(0, dp[i]));
}
std::cout << dp[n] << endl;
return 0;
}