• 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;
    }
    
  • 相关阅读:
    python爬虫系列:三、URLError异常处理
    python系列:二、Urllib库的高级用法
    python系列:一、Urllib库的基本使用
    二十七、mysql如何确保数据不丢失?有几点值得我们借鉴
    二十六、聊聊mysql如何实现分布式锁
    二十五、sql中where条件在数据库中提取与应用浅析
    Idea 使用YapiUpload上传接口到Yapi
    Yapi部署
    centos安装nodejs
    mongo部署(linux)
  • 原文地址:https://www.cnblogs.com/autoint/p/12730374.html
Copyright © 2020-2023  润新知