zoukankan      html  css  js  c++  java
  • 【LOJ#575】【LNR#2】不等关系(容斥,动态规划,分治FFT)

    【LOJ#575】【LNR#2】不等关系(容斥,动态规划,分治FFT)

    题面

    LOJ

    题解

    一个暴力(dp),设(f[i][j])表示考虑完了前(i)个位置,其中最后一个数在前面所有数中排名是第(j)大,那么转移的时候枚举一下当前数是第几大,并且满足不等式的限制就可以了,然后拿前缀和优化一下就可以做到(O(n^2))
    我们把所有连续的<看成一段,这样子题目就变成了每次要选出一段连续的上升序列,然后相邻两个连续段之间必须满足前一段的末尾要大于后一段的开头。
    显然这个大于号是不好处理的,如果我们能够任意就很好做了。
    这些>,即段与段之间的分割的位置的大小情况,如果至少有(i)个随意,方案数是(g[i]),那么对于答案的贡献就是((-1)^ig[i])
    再考虑一个(dp),我们假设分割出来的所有段中,第(i)段的长度是(a[i])。设(f[i][j])表示考虑完了前(i)段,选出了(j)个上升序列的方案数,这样子就至少有(i-j)个位置是非法的。转移的话枚举把哪一段作为一段上升序列,那么就是:

    [f[i][j]=sum_{k=0}^{i-1}f[k][j-1]*{n-s[k]choose s[i]-s[k]} ]

    其中(s)(a)的前缀和。
    不难发现第二维用处不大,因为容斥系数只有(pm 1),所以可以把容斥系数带进去直接带进去而不需要额外记录第二维来辅助计算。
    于是转移就变成了:

    [f[i]=-sum_{k=0}^{i-1}f[k]{n-s[k]choose s[i]-s[k]} ]

    发现拆开之后可以卷积,然后有一项是((s[i]-s[k])!)不太好弄,因为(s)足够小,所以把(i)变到(s[i])位置卷,这样子(s[i]-s[k])就变成了(i-k),那么对于非(s[i])的位置,把它强制弄成(0)。这样子拿分治(FFT)卷一下就好了。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    #define MOD 998244353
    #define MAX 524288
    inline int read()
    {
    	int x=0;bool t=false;char ch=getchar();
    	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    	if(ch=='-')t=true,ch=getchar();
    	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    	return t?-x:x;
    }
    int fpow(int a,int b){int s=1;while(b){if(b&1)s=1ll*s*a%MOD;a=1ll*a*a%MOD;b>>=1;}return s;}
    int W[MAX],r[MAX];
    void NTT(int *P,int opt,int len)
    {
    	int N,l=0;for(N=1;N<len;N<<=1)++l;
    	for(int i=0;i<N;++i)r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
    	for(int i=0;i<N;++i)if(i<r[i])swap(P[i],P[r[i]]);
    	for(int i=1;i<N;i<<=1)
    	{
    		int w=fpow(3,(MOD-1)/(i<<1));W[0]=1;
    		for(int k=1;k<i;++k)W[k]=1ll*W[k-1]*w%MOD;
    		for(int j=0,p=i<<1;j<N;j+=p)
    			for(int k=0;k<i;++k)
    			{
    				int X=P[j+k],Y=1ll*W[k]*P[i+j+k]%MOD;
    				P[j+k]=(X+Y)%MOD;P[i+j+k]=(X+MOD-Y)%MOD;
    			}
    	}
    	if(opt==-1)
    	{
    		reverse(&P[1],&P[N]);
    		for(int i=0,inv=fpow(N,MOD-2);i<N;++i)P[i]=1ll*P[i]*inv%MOD;
    	}
    }
    int jc[MAX],jv[MAX],inv[MAX];
    int n,a[MAX],ans,cnt;char s[MAX];
    bool book[MAX];
    int A[MAX],B[MAX],f[MAX];
    void CDQ(int l,int r)
    {
    	if(l==r)
    	{
    		if(l==0)f[l]=1;
    		else if(!book[l])f[l]=0;
    		else f[l]=1ll*f[l]*(MOD-jv[n-l])%MOD;
    		return;
    	}
    	int mid=(l+r)>>1;
    	CDQ(l,mid);
    	for(int i=l;i<=mid;++i)A[i-l]=1ll*f[i]*jc[n-i]%MOD;
    	for(int i=1;i<=r-l+1;++i)B[i]=jv[i];
    	int N;for(N=1;N<=r-l+1+mid-l+1;N<<=1);
    	NTT(A,1,N);NTT(B,1,N);
    	for(int i=0;i<N;++i)A[i]=1ll*A[i]*B[i]%MOD;
    	NTT(A,-1,N);
    	for(int i=mid+1;i<=r;++i)f[i]=(f[i]+A[i-l])%MOD;
    	for(int i=0;i<N;++i)A[i]=B[i]=0;
    	CDQ(mid+1,r);
    }
    int C(int n,int m){if(n<0||m<0||n<m)return 0;return 1ll*jc[n]*jv[m]%MOD*jv[n-m]%MOD;}
    int main()
    {
    	scanf("%s",s+1);n=strlen(s+1)+1;
    	for(int i=1;i<n;++i)if(s[i]=='>')book[i]=true,++cnt;
    	inv[0]=inv[1]=jc[0]=jv[0]=1;
    	for(int i=2;i<=n;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;
    	for(int i=1;i<=n;++i)jc[i]=1ll*jc[i-1]*i%MOD;
    	for(int i=1;i<=n;++i)jv[i]=1ll*jv[i-1]*inv[i]%MOD;
    	CDQ(0,n);
    	/*
    	f[0]=1;
    	for(int i=1;i<=n;++i)
    		if(book[i])
    			for(int j=0;j<i;++j)
    				f[i]=(f[i]+MOD-1ll*f[j]*C(n-j,i-j)%MOD)%MOD;
    	*/
    	for(int i=0;i<=n;++i)ans=(ans+f[i])%MOD;
    	if(cnt&1)ans=(MOD-ans)%MOD;
    	printf("%d
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    Objective-C Collection was mutated while being enumerated crash
    C++ assert断言
    Objective-C 禁用NSMenu中的系统services菜单项
    django----命令
    django----admin源码流程
    django----admin
    django----利用Form 实现两次密码输入是否一样 ( 局部钩子和全局钩子 )
    django----基于Form组件实现的增删改和基于ModelForm实现的增删改
    java----面试题
    课外知识----ini
  • 原文地址:https://www.cnblogs.com/cjyyb/p/11149241.html
Copyright © 2011-2022 走看看