1327F - AND Segments (区间限制下的DP计数)

题目链接

题目大意:

n n n个数字 a 1 , a 2 , . . a n a_1,a_2,..a_n a1,a2,..an组成数组 a a a m m m个限制条件,第 i i i个限制条件 [ l i , r i , x i ] [l_i,r_i,x_i] [li,ri,xi]表示需要满足 a l i & a l i . . . & a r i = x i a_{l_i}\&a_{l_i}...\&a_{r_i}=x_i ali&ali...&ari=xi
同时每个 0 ≤ a i < 2 k 0\le a_i < 2^k 0ai<2k
求有多少种满足以上条件的数组 a a a

解题思路

首先按位去求解,每一位的答案都是独立的。可以拆分成k次求解过程。
假设限制求的是第 t t t位的答案,对于第i个限制,如果这个限制的 x i x_i xi t t t位是1,则 [ l i , r i ] [l_i,r_i] [li,ri] a a a的第 t t t位只能为1.
如果第i个限制的第 t t t位是0,则 [ l i , r i ] [l_i,r_i] [li,ri] a a a的至少有一个第 t t t位是0.

问题化简为:长度为n的数组,有一些位置可以选填0/1,需要满足一些限制条件,限制条件为:一段区间 [ l , r ] [l,r] [l,r]内至少有1个0.

到这里其实有了套路,对于这种区间约束条件,利用满足当前限制的所有状态去得到下一状态而舍去非法状态数是应该要有的思想。

这里令 d p [ i ] dp[i] dp[i]为只考虑右端点 ≤ i \le i i的限制,且第 i i i位为0的填充方案数。
p r e [ i ] pre[i] pre[i]表示右端点 ≤ i \le i i的所有限制的左端点的最大值。

则推导 d p [ i ] dp[i] dp[i]的时候,只有 j ≥ p r e [ i − 1 ] j\ge pre[i-1] jpre[i1] d p [ j ] dp[j] dp[j]才能为 d p [ i ] dp[i] dp[i]提供贡献,因为它需要满足前面的所有区间限制。
所以 d p [ i ] = ∑ j = p r e [ i − 1 ] i − 1 d p [ j ] dp[i] = \sum_{j=pre[i-1]}^{i-1} dp[j] dp[i]=j=pre[i1]i1dp[j]
利用前缀和递推得到所有的 d p dp dp值。
最终的答案需要满足所有右端点 ≤ n \le n n的限制,所以单个位的答案= ∑ i = p r e [ n ] n d p [ i ] \sum_{i=pre[n]}^{n} dp[i] i=pre[n]ndp[i]

最后把每一位的答案相乘就是最终答案。

这种有区间限制的DP遇到了好几次,但是每次都会陷进去想不出来,还是要多练啊……

#include<bits/stdc++.h>
#define ll long long
#define fors(i, a, b) for(int i = (a); i < (b); ++i)
using namespace std;
const int maxn = 5e5 + 50;
const int mod =  998244353;
int l[maxn], r[maxn], x[maxn], pre[maxn];
int n, k, m;
int dp[maxn], s[maxn], d[maxn];
ll sol(int id){
    fors(i, 0, n+1) pre[i] = 0, dp[i] = s[i] = d[i] = 0;
    fors(i, 0, m){
        if(x[i]>>id&1) d[l[i]]++, d[r[i]+1]--;
        else pre[r[i]] = max(pre[r[i]], l[i]);
    }
    dp[0] = s[0] = 1;
    fors(i, 1, n+1) {
        pre[i] = max(pre[i], pre[i-1]), d[i] += d[i-1];
        if(d[i] > 0) dp[i] = 0;
        else if(pre[i-1] > 0)dp[i] = (s[i-1]-s[pre[i-1]-1])%mod;
        else dp[i] = s[i-1];
        s[i] = (s[i-1] + dp[i])%mod;
    }
    return (s[n]-s[pre[n]-1])%mod;
}
int main()
{
    cin>>n>>k>>m;
    fors(i, 0, m) scanf("%d%d%d", &l[i], &r[i], &x[i]);
    ll ans = 1;
    fors(i, 0, k) ans = ans*sol(i)%mod;
    cout<<(ans+mod)%mod<<endl;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值