首页 > 技术文章 > CF1327F题解

lmpp 2022-03-09 14:50 原文

首先第一步,位运算拆位。变为一个区间的 \(And\)\(0\)\(1\)

如果 \(And\)\(1\),那么所有数都需要为 \(1\),否则为 \(0\)

我们把所有可能为 \(0\) 的位置拉出来,然后和区间进行离散化。这个可以做到 \(O(n+m)\),处理每个位置前面第一个可能为 \(0\) 的位置即可。

问题转化为一个序列的一些区间中必须包含至少一个 \(0\)。仔细看看,好像和 命运 在链上的情况有点像。

于是。我们设上一个 \(dp[n][k]\) 表示当前从左往右扫描到第 \(n\) 个数,上一个 \(0\) 出现在第 \(k\) 个位置上。

分两种情况:是某个区间的右端点和不是某个区间的右端点。对于每个是右端点的位置,我们存下左端点中最靠右的那个设为 \(L_n\)

是右端点:

\[dp[n][k]=[L_n\leq k]dp[n-1][k] \]

\[dp[n][n]=\sum_{i=1}^{n-1}dp[n-1][i] \]

不是右端点:

\[dp[n][k]=dp[n-1][k] \]

\[dp[n][n]=\sum_{i=1}^{n-1}dp[n-1][i] \]

问题相当于每次让 \(dp[n]\) 继承 \(dp[n-1]\),然后删掉某一段左端点,然后插入一个位置为左边的位置的和。

我们可以维护一个区间 \([L,R]\) 表示目前有值的区间,然后用一个 \(sum\) 维护区间的和,然后就做完了。

答案是每一位最后的 \(sum\) 的积。

#include<cstdio>
typedef unsigned ui;
const ui M=5e5+5,mod=998244353;
ui n,k,m,l[M],r[M],x[M];
ui s[M];
inline ui max(const ui&a,const ui&b){
	return a>b?a:b;
}
inline ui Solve(const ui&k){
	ui len(0);
	static ui t[M],L[M],dp[M],pre[M];
	for(ui i=1;i<=n;++i)s[i]=0;
	for(ui i=1;i<=m;++i)if(t[i]=x[i]>>k&1)++s[l[i]],--s[r[i]+1];
	for(ui i=1;i<=n;++i)s[i]+=s[i-1];
	for(ui i=1;i<=n;++i){
		if(!s[i])++len;pre[i]=len;
	}
	for(ui i=1;i<=len;++i)L[i]=0;
	for(ui i=1;i<=m;++i)if(!t[i])L[pre[r[i]]]=max(L[pre[r[i]]],s[l[i]]?pre[l[i]]+1:pre[l[i]]);
	for(ui i=1;i<=len;++i)L[i]=max(L[i],L[i-1]);
	ui l(0),r(0);unsigned long long sum(1);dp[0]=1;
	for(ui i=1;i<=len;++i){
		while(l<L[i-1])sum-=dp[l++];dp[i]=sum%mod;sum+=dp[i];
	}
	while(l<L[len])sum-=dp[l++];
	return sum%mod;
}
signed main(){
	ui ans(1);
	scanf("%u%u%u",&n,&k,&m);
	for(ui i=1;i<=m;++i)scanf("%u%u%u",l+i,r+i,x+i);
	for(ui i=0;i<k;++i)ans=1ull*ans*Solve(i)%mod;
	printf("%u",ans);
}

推荐阅读