题意
有 n 个位置,现在要往上面填数,给定 k,每个数小于 2k。现在有 m 个限制条件,每个限制条件给定 li,ri,xi,要求满足 a[li]&a[li+1]&…&a[ri]=xi。求合法的填数方案数。
分析
因为 & 运算是每位独立的,所以这题填的数每一位也是独立的,我们可以一位一位考虑。
那么限制条件转化为以下两种情况:
- 整个区间都填 1
- 整个区间中存在至少一个 0
那么我们只用考虑哪些位置填 0 即可。
设 fi,j 表示考虑到第 i 位,最后一个填 0 的位置为 j 的方案数。
设 gi,j=j=0∑ifi,j
转移分两种情况:
- 不在第 i 位填 0
fi,j=fi−1,j - 在第 i 位填 0
fi,i=j=0∑i−1fi−1,j=gi−1,i−1
看起来做完了,其实还没有!
如果 i 是某个存在 0 的限制的右端点,如图:
这个图是多个以 i 为右顶点的存在 0 的区间, lmax 是最大的左端点。
那么最近一个 0 的位置肯定是在 [lmax,i] 中的。因此, f0,lmax−1 的值都应该被清 0。
到了这里,我们得到了一个 O(kn2) 的做法:
每次暴力更新 fi,j,且每次暴力清 0
考虑优化。
看一下转移方程,设目前来到第 i 位,如果用滚动数组,转移方程将变为:
- 不在第 i 位填 0
fj=fj - 在第 i 位填 0
fi=gi−1
那么转移变成 O(1) 的了。
考虑清 0 操作,我们维护上一次清 0 的点为 p,这次清 0,相当于将 [p,lmax−1] 清 0,然后将 p 移动到 lmax。这样,每个点只会被清 0 一次。
总的复杂度为 O(kn+km)。
代码如下
#include <bits/stdc++.h>
#define N 500005
using namespace std;
typedef long long LL;
typedef unsigned long long uLL;
const int mod = 998244353;
LL z = 1;
int read(){
int x, f = 1;
char ch;
while(ch = getchar(), ch < '0' || ch > '9') if(ch == '-') f = -1;
x = ch - '0';
while(ch = getchar(), ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - 48;
return x * f;
}
int cnt[N], mx[N], f[N], g[N];
struct node{
int l, r, x;
}d[N];
int main(){
int t, i, j, k, n, m, l, r, ans = 1, p;
n = read(); k = read(); m = read();
for(i = 1; i <= m; i++) d[i].l = read(), d[i].r = read(), d[i].x = read();
for(t = 0; t < k; t++){
for(i = 1; i <= n + 1; i++) f[i] = g[i] = mx[i] = cnt[i] = 0;
for(i = 1; i <= m; i++){
l = d[i].l; r = d[i].r;
if(1 << t & d[i].x) cnt[l]++, cnt[r + 1]--;
else mx[r] = max(mx[r], l);
}
for(i = 1; i <= n; i++) cnt[i] += cnt[i - 1];
f[0] = g[0] = 1;
p = 0;
for(i = 1; i <= n; i++){
g[i] = g[i - 1];
if(!cnt[i]){
f[i] = g[i - 1];
g[i] = (g[i] + f[i]) % mod;
}
while(p < mx[i]) g[i] = (g[i] - f[p]) % mod, p++;
}
ans = z * ans * g[n] % mod;
}
ans = (ans % mod + mod) % mod;
printf("%d", ans);
return 0;
}