20200713 T1 集合划分(JOISCD1T1)【多项式优化DP】

17 篇文章 1 订阅

题目描述

长度为 2 n 2n 2n { A i } , { B i } \{A_i\},\{B_i\} {Ai},{Bi} 序列,现在要选出一个新序列 { C i } \{C_i\} {Ci},其中 C i = A i C_i=A_i Ci=Ai C i = B i C_i=B_i Ci=Bi

要求 C i ≤ C i + 1 C_i\le C_{i+1} CiCi+1,且选择的 A A A 的个数恰为 n n n 个,问方案数,方案不同当且仅当某个位置的选择不同。

n ≤ 5 ∗ 1 0 4 , m o d    998244353 n\le 5*10^4,\mod 998244353 n5104,mod998244353

题目分析

做法一

f [ i ] [ 0 / 1 ] [ j ] f[i][0/1][j] f[i][0/1][j] 表示选到了 C i C_i Ci C i C_i Ci 的选择是 A / B A/B A/B A A A 选了 j j j 个的方案数。

普通的转移就看 A i − 1 , A i , B i − 1 , B i A_{i-1},A_i,B_{i-1},B_i Ai1,Ai,Bi1,Bi 的大小关系来转移。

每次转移 j j j 只会增加 1 或者不变,可以写出 f [ i ] [ 0 / 1 ] f[i][0/1] f[i][0/1] 的生成函数 F i , 0 / 1 = ∑ f [ i ] [ 0 / 1 ] [ j ] x j F_{i,0/1}=\sum f[i][0/1][j]x^j Fi,0/1=f[i][0/1][j]xj

那么初始矩阵就是 [ 1 0 ] \begin{bmatrix}1&0\end{bmatrix} [10],每个位置的转移矩阵就是 [ 0 / x 0 / 1 0 / x 0 / 1 ] \begin{bmatrix}0/x &0/1\\0/x&0/1\end{bmatrix} [0/x0/x0/10/1]

分治NTT即可。

Code(考试的时候两个vector都为空的最后resize(N+M-1)就RE了。):

#include<bits/stdc++.h>
#define maxn 150005
using namespace std;
const int mod = 998244353;
typedef vector<int> Poly;
int w[maxn],r[maxn],WL,lg[maxn];
int Pow(int a,int b){int s=1;for(;b;b>>=1,a=1ll*a*a%mod) b&1&&(s=1ll*s*a%mod);return s;}
void init(int n){
	w[0]=WL=1; while(WL<=n) WL<<=1; w[1]=Pow(3,(mod-1)/WL);
	for(int i=2;i<=WL;i++) w[i]=1ll*w[i-1]*w[1]%mod,lg[i]=lg[i>>1]+1;
}
int upd(int x){return x+(x>>31&mod);}
void NTT(Poly &a,int len,int flg){
	for(int i=0;i<len;i++) if(i<(r[i]=r[i>>1]>>1|(i&1?len>>1:0))) swap(a[i],a[r[i]]);
	for(int i=2,l=1,v;i<=len;l=i,i<<=1)
		for(int j=0,t=WL/i;j<len;j+=i)
			for(int k=j,o=0;k<j+l;k++,o+=t)
				a[k+l]=upd(a[k]-(v=1ll*w[flg^1?WL-o:o]*a[k+l]%mod)),a[k]=upd(a[k]+v-mod);
	if(flg^1) for(int i=0,Inv=mod-(mod-1)/len;i<len;i++) a[i]=1ll*a[i]*Inv%mod;
}
void Add(Poly &a,Poly &b,Poly &c){
	int n=a.size();
	for(int i=0;i<n;i++) a[i]=(a[i]+1ll*b[i]*c[i])%mod;
}
int n,a[maxn],b[maxn];
struct Mat{
	Poly s[2][2];
	void operator *= (Mat &B){
		int N=0,M=0;
		for(int i=0;i<2;i++) for(int j=0;j<2;j++) N=max(N,(int)s[i][j].size()),M=max(M,(int)B.s[i][j].size());
		if(!N||!M) {for(int i=0;i<2;i++) for(int j=0;j<2;j++) s[i][j].clear(); return;}
		int len=1<<lg[N+M-2]+1;
		Mat ret;
		for(int i=0;i<2;i++) for(int j=0;j<2;j++)
			s[i][j].resize(len),NTT(s[i][j],len,1),B.s[i][j].resize(len),NTT(B.s[i][j],len,1),
			ret.s[i][j].resize(len);
		for(int k=0;k<2;k++) for(int i=0;i<2;i++) for(int j=0;j<2;j++) Add(ret.s[i][j],s[i][k],B.s[k][j]);
		for(int i=0;i<2;i++) for(int j=0;j<2;j++) NTT(ret.s[i][j],len,-1),ret.s[i][j].resize(N+M-1);
		*this=ret;
	}
}M[maxn];
void solve(int l,int r){
	if(l==r) return;
	int mid=(l+r)>>1;
	solve(l,mid),solve(mid+1,r);
	M[l]*=M[mid+1];
	
}
int main()
{
	freopen("divide.in","r",stdin);
	freopen("divide.out","w",stdout);
	scanf("%d%*d",&n),init(2*n);
	for(int i=1;i<=2*n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=2*n;i++) scanf("%d",&b[i]);
	for(int i=1;i<=2*n;i++){
		if(a[i-1]<=a[i]) M[i].s[0][0]=Poly{0,1};
		if(b[i-1]<=a[i]) M[i].s[1][0]=Poly{0,1};
		if(a[i-1]<=b[i]) M[i].s[0][1]=Poly{1};
		if(b[i-1]<=b[i]) M[i].s[1][1]=Poly{1};
	}
	solve(1,2*n);
	printf("%d\n",((M[1].s[0][0].size()>n?M[1].s[0][0][n]:0)+(M[1].s[0][1].size()>n?M[1].s[0][1][n]:0))%mod);
	//cerr<<clock()<<endl;
}
做法二

考虑前后两个位置的选择,有些是确定的,有些是会影响的:
在这里插入图片描述
在这里插入图片描述

做法三

与做法一类似,不过可能想法更自然:
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值