CodeForces1327 F. AND Segments(dp+优化)

题意:

给定n,k,m,
和m个条件(l,r,x)
要求构造一个长度为n的数组a,满足:
1.0<=a(i)<2k
2.对于每个条件(l,r,x),a数组中[l,r]的区间与&等于x

问有多少种这样的数组,答案对998244353取模

解法:

因为每一位之间是互不相关的,因此按位计算方案数,最后相乘即可

对于每个条件(l,r,x):
1.如果x的第k位为1,那么[l,r]的第k位都必须为1
2.如果x的第k位为0,那么[l,r]的第k位至少有一个为0

对于第一种情况,可以利用差分前缀和计算出最后哪些位置必须为1

令d[i][j]为前i个位置最后一个0在位置j上,组成的满足条件的方案数

先不考虑[l,r]至少有一个为0的情况,
对于第i个位置:
如果这个位置必须填1,那么d[i][j]=d[i-1][j],即只有一种填法,方案数不变
如果这个位置可填10,1:d[i][j]=d[i-1][j],0:d[i][i]=sigma{d[i-1][k]},其中1<=k<=i-1;

现在考虑[l,r]至少有一个为0的情况
加入当前计算到位置i,[l,i]中至少有一个0
那么d[i][0]到d[i][l-1]都需要变为0,因为都不满足条件

以上全部情况都讨论完了,但是观察到n<=5e5,数组开不下,复杂度也太大,因此考虑优化:
显然d[i][j]只需要用到d[i-1][1到j],那么滚动数组优化,用d[j]就表示上一个0在位置j的方案数
发现填1的时候方案数不变,0的时候方案数为d[i-1][k]的前缀和,即d[j]的前缀和
用sum存d[k]的前缀和,则sum就是当前i的方案数
如何清空呢,因为每次清空都是清空1-(L-1)的位置,只需要记录L就行了
所以用last表示清空到的位置(L-1),
当存在[L,i]至少一个0的时候,把前缀和sum-=d[last++],直到last大于L-1
这样就优化到O(n)

code:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=5e5+5;
const int mod=998244353;
struct Node{
    int l,r,x;
}e[maxm];
int cnt[maxm];
int pre[maxm];
int d[maxm];
int n,k,m;
int ans=1;
int solve(int now){
    for(int i=0;i<=n;i++)d[i]=pre[i]=cnt[i]=0;
    for(int i=1;i<=m;i++){
        if(e[i].x>>now&1)cnt[e[i].l]++,cnt[e[i].r+1]--;//差分
        else pre[e[i].r]=max(pre[e[i].r],e[i].l);//清空1-L且清空1-LL,即清空1-max(L,LL)
    }
    for(int i=1;i<=n;i++)cnt[i]+=cnt[i-1];//差分数组的前缀和
    int sum=d[0]=1,last=0;
    for(int i=1;i<=n;i++){
        if(!cnt[i])d[i]=sum,sum=sum*2%mod;
        while(last<pre[i])sum=(sum-d[last++]+mod)%mod;
    }
    return sum;
}
signed main(){
	ios::sync_with_stdio(0);cin.tie(0);
    cin>>n>>k>>m;
    for(int i=1;i<=m;i++)cin>>e[i].l>>e[i].r>>e[i].x;
    for(int i=0;i<k;i++)ans=ans*solve(i)%mod;
    cout<<ans<<endl;
    return 0;
}

发布了445 篇原创文章 · 获赞 37 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/weixin_44178736/article/details/105181612
今日推荐