CF1327F AND Segments(滚动数组的妙用)

该博客介绍了如何利用滚动数组优化解决CF1327F问题,即在限定条件下计算合法填数方案数。通过分析题目中按位运算的特点,将问题分解为考虑填0的位置,然后建立状态转移方程,并利用滚动数组将复杂度从O(kn^2)降低到O(kn+km),实现了效率的提升。

题意

nnn 个位置,现在要往上面填数,给定 kkk,每个数小于 2k2^k2k。现在有 mmm 个限制条件,每个限制条件给定 li,ri,xil_i,r_i,x_ili,ri,xi,要求满足 a[li]&a[li+1]&…&a[ri]=xia[l_i] \& a[l_i + 1] \& \dots \& a[r_i] = x_ia[li]&a[li+1]&&a[ri]=xi。求合法的填数方案数。

分析

因为 &\&& 运算是每位独立的,所以这题填的数每一位也是独立的,我们可以一位一位考虑。
那么限制条件转化为以下两种情况:

  • 整个区间都填 111
  • 整个区间中存在至少一个 000

那么我们只用考虑哪些位置填 000 即可。
fi,jf_{i,j}fi,j 表示考虑到第 iii 位,最后一个填 000 的位置为 jjj 的方案数。
gi,j=∑j=0ifi,jg_{i,j}=\sum\limits_{j=0}^{i}f_{i,j}gi,j=j=0ifi,j
转移分两种情况:

  • 不在第 iii 位填 000
    fi,j=fi−1,jf_{i,j}=f_{i-1,j}fi,j=fi1,j
  • 在第 iii 位填 000
    fi,i=∑j=0i−1fi−1,j=gi−1,i−1f_{i,i}=\sum\limits_{j=0}^{i-1}f_{i-1,j}=g_{i-1,i-1}fi,i=j=0i1fi1,j=gi1,i1

看起来做完了,其实还没有!
如果 iii 是某个存在 000 的限制的右端点,如图:
在这里插入图片描述
这个图是多个以 iii 为右顶点的存在 000 的区间,lmaxlmaxlmax 是最大的左端点。
那么最近一个 000 的位置肯定是在 [lmax,i][lmax,i][lmax,i] 中的。因此,f0,lmax−1f_{0,lmax-1}f0,lmax1 的值都应该被清 000
到了这里,我们得到了一个 O(kn2)O(kn^2)O(kn2) 的做法:
每次暴力更新 fi,jf_{i,j}fi,j,且每次暴力清 000
考虑优化。
看一下转移方程,设目前来到第 iii 位,如果用滚动数组,转移方程将变为:

  • 不在第 iii 位填 000
    fj=fjf_j=f_{j}fj=fj
  • 在第 iii 位填 000
    fi=gi−1f_i=g_{i-1}fi=gi1

那么转移变成 O(1)O(1)O(1) 的了。
考虑清 000 操作,我们维护上一次清 000 的点为 ppp,这次清 000,相当于将 [p,lmax−1][p,lmax-1][p,lmax1]000,然后将 ppp 移动到 lmaxlmaxlmax。这样,每个点只会被清 000 一次。
总的复杂度为 O(kn+km)O(kn+km)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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值