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的贡献,否则没有,所以
然后我们考虑如何考虑\(B\)
我们知道,所有数都不大于\(n\),所以\(n\)左边的数才可能贡献到\(A\),\(n\)右边的数才可能贡献到\(B\),所以
但是这个式子还是要进行优化的,不过有一点比较麻烦,第一类Stirling数连通项公式都不好求,怎么推式子呢?
我们可以用组合意义来证明等式。
\(Ans\)是在\(n-1\)个元素中先选出\(i-1\)个,然后再分别将\(i-1\)个和剩下的\(n-i\)个组成\(a-1\)和\(b-1\)个圆排列(这是根据元素个数来枚举)
也是\(n-1\)个元素组成\(a+b-2\)个圆排列,然后再这\(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\) 的排列个数,我们考虑插入一个数会发生什么。
如果我们把这个数放在最前面,那么前缀最大值会加一;如果我们把这个数放在其它位置,前缀最大值不变,因此我们有状态转移方程:
但是这个和答案有什么联系呢?观察发现,整个排列中一定有一个最高点,且在最高点左边,我们有 \(j - 1\) 个前缀最大值。考虑将后面的序列反转,那么后缀最大值也转换为了前缀最大值的问题。也就是说,我们相当于要将两个排列组合起来。(好像很难想的样子)
我们枚举最大值的位置\(i+1\)和最大值左边选哪些数,就能得到最后的答案:
我们可以看看能否继续化简答案的\(O(n^2)\)式子,或者改变算式的形式。
首先对于每种满足要求的排列,我们按以下方式将\(1\dots n-1\)分成\(a+b-2\)组:
对于\(n\)的左边,每个作为前缀最大值的数的数一直到下一个作为的数或者\(n\)的前一位分为一组,右边类似。
然后我们去掉\(n\),把右边的翻转,再按组排序,发现得到的结果的方案数就是\(f_{n - 1, a + b - 2}\)。现在我们把\(n\)的插到第 \(a - 1\) 组之后,然后翻转右边那部分,这样我们就得到了一个符合题目要求的排列!所以我们找到了一个一一映射。
所以要求的就是\(f\),而我们早就发现他是轮换数。FFT预处理即可,时间复杂度\(O(n\log n)\)。
不用DP式看出轮换数的话,也可以直接考虑组合意义。
去掉\(n\)之后,假设某组有\(i\)个数,那么除了最大的那个数,其他数可以随意排列,有\((i-1)!\)种方案,注意到\(i\)个数的轮换的方案数\(\begin{bmatrix}i \\ 1\end{bmatrix}=(i-1)!\),所以我们可以将这种分组看做把\(n-1\)个元素划分为\(a+b-2\)个轮换,方案数为\(\begin{bmatrix}n-1\\a+b-2\end{bmatrix}\)。
以上并没有考虑\(n\),我们分好组后,还要决定把哪些组排在\(n\)的左边,方案数为\(\binom{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\n",mul(f[a+b-2],mul(fac[a+b-2],mul(ifac[a-1],ifac[b-1]))));
return 0;
}