FJOI2016 建筑师 和 CF960G Bandit Blues

过了一年来看,这道题还是很妙。

FJOI2016 建筑师

小 Z 是一个很有名的建筑师,有一天他接到了一个很奇怪的任务:在数轴上建 (n) 个建筑,每个建筑的高度是 (1)(n) 之间的一个整数。

小 Z 有很严重的强迫症,他不喜欢有两个建筑的高度相同。另外小 Z 觉得如果从最左边(所有建筑都在右边)看能看到 (A) 个建筑,从最右边(所有建筑都在左边)看能看到 (B) 个建筑,这样的建筑群有着独特的美感。现在,小 Z 想知道满足上述所有条件的建筑方案有多少种?

如果建筑 (i) 的左(右)边没有任何建造比它高,则建筑 (i) 可以从左(右)边看到。两种方案不同,当且仅当存在某个建筑在两种方案下的高度不同。

对于 (100 \%) 的数据 :(1 leq n leq 50000, 1 leq A, B leq 100, 1 leq T leq 200000)

题解

https://www.luogu.com.cn/blog/WDLGZH2017/solution-p4609

首先(A)(B)的地位是对称的,我们可以先考虑只有(A)的限制条件怎么做。

(dp[i][j])表示对于(i)个元素的排列,且(A=j)的方案数。

显然如果最小的数在第一位,那么就有1的贡献,否则没有,所以

[dp[i][j]=dp[i-1][j-1]+(i-1) imes dp[i-1][j] ]

然后我们考虑如何考虑(B)

我们知道,所有数都不大于(n),所以(n)左边的数才可能贡献到(A)(n)右边的数才可能贡献到(B),所以

[Ans=sum_{i=1}^negin{bmatrix}i-1\A-1end{bmatrix} imes egin{bmatrix}n-i\B-1end{bmatrix} imes inom{n-1}{i-1} ]

但是这个式子还是要进行优化的,不过有一点比较麻烦,第一类Stirling数连通项公式都不好求,怎么推式子呢?

我们可以用组合意义来证明等式。

(Ans)是在(n-1)个元素中先选出(i-1)个,然后再分别将(i-1)个和剩下的(n-i)个组成(a-1)(b-1)个圆排列(这是根据元素个数来枚举)

也是(n-1)个元素组成(a+b-2)个圆排列,然后再这(a+b-2)个圆排列中选(a-1)个(这是根据圆排列直接枚举),因此得知

[Ans=egin{bmatrix}n-1\a+b-2end{bmatrix} imes inom{a+b-2}{a-1} ]

CF960G Bandit Blues

给你三个正整数 (n)(a)(b),定义 (A) 为一个排列中是前缀最大值的数的个数,定义 (B) 为一个排列中是后缀最大值的数的个数,求长度为 (n) 的排列中满足 (A = a)(B = b) 的排列个数。(n le 10^5),答案对 (998244353) 取模。

题解

对于 DP,除了插入最大的,我们还能插入最小的。

我们强制让原来的排列为 (2 sim n),让要插入的数为 (1),这样我们就能惊喜地发现,好像可以设状态了。

(f_{i, j}) 表示由 (i) 个数组成且前缀最大值为 (j) 的排列个数,我们考虑插入一个数会发生什么。

如果我们把这个数放在最前面,那么前缀最大值会加一;如果我们把这个数放在其它位置,前缀最大值不变,因此我们有状态转移方程:

[f_{i, j} = f_{i - 1, j - 1} + (i - 1) imes f_{i - 1, j} ]

但是这个和答案有什么联系呢?观察发现,整个排列中一定有一个最高点,且在最高点左边,我们有 (j - 1) 个前缀最大值。考虑将后面的序列反转,那么后缀最大值也转换为了前缀最大值的问题。也就是说,我们相当于要将两个排列组合起来。(好像很难想的样子)

我们枚举最大值的位置(i+1)和最大值左边选哪些数,就能得到最后的答案:

[mathrm{Ans} = sum_{i = a - 1}^{n - b} f_{i, a - 1} imes f_{n - 1 - i, b - 1} imes inom{n - 1}{i} ]

我们可以看看能否继续化简答案的(O(n^2))式子,或者改变算式的形式。

首先对于每种满足要求的排列,我们按以下方式将(1dots n-1)分成(a+b-2)组:
对于(n)的左边,每个作为前缀最大值的数的数一直到下一个作为的数或者(n)的前一位分为一组,右边类似。

然后我们去掉(n),把右边的翻转,再按组排序,发现得到的结果的方案数就是(f_{n - 1, a + b - 2})。现在我们把(n)的插到第 (a - 1) 组之后,然后翻转右边那部分,这样我们就得到了一个符合题目要求的排列!所以我们找到了一个一一映射。

[mathrm{Ans} = f_{n - 1, a + b - 2} imes inom{a + b - 2}{a - 1} ]

所以要求的就是(f),而我们早就发现他是轮换数。FFT预处理即可,时间复杂度(O(nlog n))


不用DP式看出轮换数的话,也可以直接考虑组合意义。

去掉(n)之后,假设某组有(i)个数,那么除了最大的那个数,其他数可以随意排列,有((i-1)!)种方案,注意到(i)个数的轮换的方案数(egin{bmatrix}i \ 1end{bmatrix}=(i-1)!),所以我们可以将这种分组看做把(n-1)个元素划分为(a+b-2)个轮换,方案数为(egin{bmatrix}n-1\a+b-2end{bmatrix})

以上并没有考虑(n),我们分好组后,还要决定把哪些组排在(n)的左边,方案数为(inom{a+b-2}{a-1})。所以最终答案殊途同归。

void num_trans(polynomial&a,int dir){
	int lim=a.size();
	static vector<int> rev,w[2];
	if(rev.size()!=lim){
		rev.resize(lim);
		int len=log2(lim);
		for(int i=0;i<lim;++i)
			rev[i]=rev[i>>1]>>1|(i&1)<<(len-1);
		for(int dir=0;dir<2;++dir){
			w[dir].resize(lim);
			w[dir][0]=1,w[dir][1]=fpow(g[dir],(mod-1)/lim);
			for(int i=2;i<lim;++i)	
				w[dir][i]=mul(w[dir][i-1],w[dir][1]);
		}
	}
	for(int i=0;i<lim;++i)
		if(i<rev[i]) swap(a[i],a[rev[i]]);
	for(int step=1;step<lim;step<<=1){
		int quot=lim/(step<<1);
		for(int i=0;i<lim;i+=step<<1){
			int j=i+step;
			for(int k=0;k<step;++k){
				int t=mul(w[dir][quot*k],a[j+k]);
				a[j+k]=add(a[i+k],mod-t),a[i+k]=add(a[i+k],t);
			}
		}
	}
	if(dir){
		int ilim=fpow(lim,mod-2);
		for(int i=0;i<lim;++i) a[i]=mul(a[i],ilim);
	}
}

co int N=200000+1;
int fac[N],ifac[N];

polynomial solve(int n){ // [0,n]
	if(!n) return polynomial(1,1);
	if(n==1) {
		polynomial a(2);
		return a[1]=1,a;
	}
	int len=n>>1;
	polynomial a=solve(len);
	polynomial b(len+1),c(len+1);
	for(int i=0;i<=len;++i) b[i]=mul(a[i],fac[i]);
	for(int i=0;i<=len;++i) c[len-i]=mul(fpow(len,i),ifac[i]);
	int lim=1<<int(ceil(log2((len<<1)+1)));
	b.resize(lim),c.resize(lim);
	num_trans(b,0),num_trans(c,0);
	for(int i=0;i<lim;++i) c[i]=mul(c[i],b[i]);
	num_trans(c,1);
	b.resize(len+1);
	for(int i=0;i<=len;++i) b[i]=mul(c[i+len],ifac[i]);
	a.resize(lim),b.resize(lim);
	num_trans(a,0),num_trans(b,0);
	for(int i=0;i<lim;++i) a[i]=mul(a[i],b[i]);
	num_trans(a,1),a.resize((len<<1)+1);
	if(n&1){
		a.resize(n+1);
		int x=a[0],y;
		for(int i=1;i<=n;++i,x=y)
			y=a[i],a[i]=add(x,mul(n-1,y));
	}
	return a;
}
int main(){
	int n=read<int>(),a=read<int>(),b=read<int>();
	fac[0]=1;
	for(int i=1;i<=n;++i) fac[i]=mul(fac[i-1],i);
	ifac[n]=fpow(fac[n],mod-2);
	for(int i=n-1;i>=0;--i) ifac[i]=mul(ifac[i+1],i+1);
	polynomial f=solve(n-1);f.resize((n<<1)-1);
	printf("%d
",mul(f[a+b-2],mul(fac[a+b-2],mul(ifac[a-1],ifac[b-1]))));
	return 0;
}
原文地址:https://www.cnblogs.com/autoint/p/12730374.html