过了一年来看,这道题还是很妙。
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))式子,或者改变算式的形式。
首先对于每种满足要求的排列,我们按以下方式将(1dots n-1)分成(a+b-2)组:
对于(n)的左边,每个作为前缀最大值的数的数一直到下一个作为的数或者(n)的前一位分为一组,右边类似。
然后我们去掉(n),把右边的翻转,再按组排序,发现得到的结果的方案数就是(f_{n - 1, a + b - 2})。现在我们把(n)的插到第 (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;
}