首页 > 其他分享 >CF-1831-E-卡特兰数+异或哈希+差分

CF-1831-E-卡特兰数+异或哈希+差分

时间:2024-01-22 19:33:06浏览次数:34  
标签:1831 int 区间 CF 括号 序列 fac 卡特兰

1831-E 题目大意

给定一个整数\(n\),和\(k\)个区间,区间端点范围在\([1,n]\)内。

如果有一个长为\(n\)合法的括号序列,且它的这\(k\)个区间\([l,r]\)中的子括号序列也是合法的,那么称这个括号序列是“好的”。

请你求出有多少个长度为\(n\)的“好的”括号序列,答案对\(998244353\)取模。


Solution

从简单的情况开始考虑:

\(1.k=0\)时:
容易知道一个长为\(2n\)的合法括号序列个数为卡特兰数\(\frac{1}{n+1}\binom{2n}{n}\)。

\(2.k=1时\):
要求区间\([l,r]\)中是一个合法的括号序列,因为原括号序列也是合法的,那么我们将\([1,l]\)和\([r+1,n]\)两段拼起来也是一个合法的的括号序列,据乘法原理,把两个括号序列的卡特兰数乘起来就是总的方案数。

\(3.k=2时\):
两个区间\([l_1,r_1],[l_2,r_2]\)其中\(l_1\le l_2\),分三种情况:

  • 两个区间相离\((r_1<l_2)\):可以把整个括号序列分为三段,\([l_1,r_1],[l_2,r_2]\)以及除这两个区间之外的拼起来的一段,此时把三个括号序列的卡特兰数乘起来即为总方案数。
  • 两个区间包含\((r_1>r_2)\):可以把整个括号序列分为三段,\([l_2,r_2]\)一段,\([l_1,l_2-1]\)和\([r_2+1,r_1]\)拼起来的一段,\([1,l_1-1]\)和\([r_1+1,n]\)拼起来的一段,此时把三个括号序列的卡特兰数乘起来即为总方案数。
  • 两个区间相交\((r_1\ge l_2 \;and\; r_1\le r_2)\):这里序列\([l_2,r_1]\)被两个区间覆盖,那么要求这个序列也是一个合法的括号序列。此时可以把整个括号序列分为四段,\([l_1,l_2-1],[l_2,r_1],[r_1+1,r_2]\)以及\([1,l_1-1]\)和\([r_2+1,n]\)拼起来的一段,此时把四个括号序列的卡特兰数乘起来即为总方案数。

推广到一般,被相同的区间覆盖的点集应当被划分为一组,这些点对应的括弧拼起来是一个合法的括号序列,答案即把所有组的卡特兰数相乘。接下来要解决的是如何对各个点进行划分。

对于给定的一个区间\([l,r]\),我们可以对这个区间打上标记,常用的做法是用差分数组,在数组的\(l\)位置打一个\(+1\)的标记,在\(r+1\)的位置打一个\(-1\)的标记。如果用加法的话,对于两个不相交的区间,其在差分数组中对应的区间标记都是\(+1\),而实际上不能把这两个区间划分为同一组,这里的标记要用到一个\(trick\)——\(Xor\;Hashing\)。

对每个区间\([l,r]\)赋一个随机值\(x\)作为它的标记,然后在差分数组的\(l\)位置打一个\(\oplus x\)的标记,在\(r+1\)的位置也打一个\(\oplus x\)的标记($\oplus $是异或符号)。统一处理完所有区间后,对差分数组求前缀异或,即可得到每个点属于哪个分组,用哈希表统计每组中点的数量即可。最后把所有卡特兰数乘起来即可得到答案。

时间复杂度\(O(n+klogk·logmod)\)。这里用了\(map\)复杂度上多了个\(logk\),\(logmod\)是求逆元的复杂度。

#include<bits/stdc++.h>
using namespace std;
using ll=long long;

mt19937_64 rng(chrono::steady_clock::now().time_since_epoch().count());
const int mod=998244353;

struct Comb{
    constexpr static int mod=998244353;
    vector<int> fac;
    int n;

    Comb(int _n):n(_n),fac(_n+1){
        get_fac();
    };

    ll pow(ll a,ll b){
        ll res=1;
        while(b){
            if(b&1) res=res*a%mod;
            a=a*a%mod;
            b>>=1;
        }
        return res;
    }

    ll inv(ll x){
        return pow(x,mod-2);
    }

    void get_fac(){
        fac[0]=1;
        for(int i=1;i<=n;i++) fac[i]=1LL*fac[i-1]*i%mod;
    }

    int comb(int n,int m){
        if(m>n) return 0;
        return 1LL*fac[n]*inv(1LL*fac[n-m]*fac[m]%mod)%mod;
    }
};

void solve(){
    int n,k;
    cin>>n>>k;
    vector<unsigned ll> d(n+1);
    for(int i=0;i<k;i++){
        int l,r;
        cin>>l>>r;
        l--;
        auto x=rng();
        d[l]^=x;
        d[r]^=x;
    }
    for(int i=1;i<=n;i++) d[i]^=d[i-1];
    map<unsigned ll,int> p;
    for(int i=0;i<n;i++) p[d[i]]++;
    ll ans=1;
    Comb C(n+1);
    for(auto [x,c]:p){
        if(c&1){
            ans=0;
        }else{
            ans=(ans*C.comb(c,c/2)%mod)*C.inv(c/2+1)%mod;
        }
    }
    cout<<ans<<'\n';
}

int main(){
    ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    //freopen("input.txt","r",stdin);
	//freopen("output.txt","w",stdout);
    int T=1;
    cin>>T;
    while(T--){
        solve();
    }
    return 0;
}

标签:1831,int,区间,CF,括号,序列,fac,卡特兰
From: https://www.cnblogs.com/fengxue-K/p/17980817

相关文章

  • 从CF1875C学习lowbit运算判断是否为 2 的 k 次幂
    Problem-1875C-Codeforces本题判断无解的时候要判断该数是否为2的k次幂,我的做法是预处理出2的次幂数表。看题解发现可以用lowbit操作。lowbit操作intlowbit(intx){returnx&(-x);}根据补码原理,该操作可以求出来X最靠右的\(1\)构成的数。判断\(x\)......
  • 从CF1878E学习前缀和维护二进制拆位分析思想
    Problem-1878E-Codeforces这题我想到了个大概,按位与的话结果肯定是递减的,而且要从二进制每一位下手,但是思路只停留在暴力对整个数操作。当然,利用这个性质,肯定要二分。拆位思想比如要计算111000111011100100010我们知道最后结果肯定是留下都有\(1\)的位0100000......
  • CF1922F Replace on Segment
    看到有区间操作,结合\(n\le100\)的数据范围,直接考虑区间dp。设\(f_{l,r,x}\)表示将区间\([l,r]\)全部替换成\(x\)的最小步数。首先有\(f_{l,r,x}=\max_{p=l}^{r-1}f_{l,p,x}+f_{p+1,r,x}\),但这无法将该状态下的所有的情况都转移到,所以考虑再设一个\(g_{l,r,x}\)表示......
  • CF1770C
    分析不难发现,由于\(x>0\),所以当出现两个相同数的时候一定是NO。再然后,发现对于一个数\(k\),记\(re_i\)表示\(a\)中模\(k\)余\(i\)的数的个数,若对于所有的\(0\lei<k\),都有\(re_i>1\),那么不管你加多少,必定有至少\(2\)个\(k\)的倍数,不互质,因此也是NO。剩下的就是......
  • CF-1399-E2-优先队列
    1399-E2题目大意给定一棵\(n\)个节点的树,边带权,根节点为\(1\)。再给定一个整数\(S\),你可以执行以下操作:选择一条权值为\(w_i\)的边,令\(w_i\rightarrow\lfloor\frac{w_i}{2}\rfloor\)。你可以执行任意次操作,使得\(\sum_{x∈leaves}sum(1,x)\)不大于\(S\),其中\(sum(1,x)\)......
  • CF1920D. Array Repetition
    思路用一个数组len记录每次操作后数组的长度,用一个数组lat记录每次操作后数组最后一个数字。对于每次询问,先二分查找出第几次操作能使数组的长度大于等于xac代码#include<bits/stdc++.h>usingnamespacestd;usingi64=longlong;consti64inf=1e18;typedefpair<in......
  • CF-461-B-树形DP
    461-B题目大意给定一棵\(n\)个节点的树,节点编号从\(0\)开始,每个节点要么为白色要么为黑色,你需要删除一些边,使得剩下的各个连通块中有且仅有一个黑色节点。问有多少种删边方案数,答案对\(10^9+1\)取模。Solution考虑树形DP,令\(dp[x][0/1]\)表示节点\(x\)属于无黑色节点/有黑色......
  • CF1920E 题解
    CF1920E被这种题卡了,脸都不要了。仔细读题,发现序列可以分成两部分(\(0\)和\(1\))来考虑。在合法序列中,对于一个\(1\),它产生的子串贡献一定是(假设与上一个\(1\)之间有\(x\)个\(0\),与下一个\(1\)之间有\(y\)个\(0\)):\[(x+1)(y+1)\]如果去DP这\(n\)个\(1\),易得转......
  • CF455D Serega and Fun 题解
    题目链接:CF或者洛谷本题是可以用平衡树去做的,具体的为每个\(k\)开一棵平衡树去维护相对位置,而这种移动操作用平衡树维护又是很容易做到的,这种做法是双\(log\)。在\(1e5\)的数据下,我们来说说好写的分块该如何去写。黑色的代表一个块,考虑暴力修改情况,假如原来的数字为\([1......
  • CF-570-D-启发式合并
    570-D题目大意给定一棵\(n\)个节点的树,根节点为\(1\),每个节点上有一个小写字母\(ch\)。定义节点\(x\)的深度为\(x\)到根节点的路径上的节点数量。\(q\)次询问,每次询问查询以\(x\)为根的子树之中所有深度为\(d\)的节点上字母重排之后是否可以构成一个回文串。Solution对于一组......