1. 程式人生 > 實用技巧 >BZOJ4402 Claris的劍

BZOJ4402 Claris的劍

題目來源:BZOJ4402 Claris的劍,後被選入【2020六校聯考NOIP 第1場】(搬題人memset0)。

演算法:組合計數。

題目連結

“本質不同”這個要求比較毒瘤,我們必須先把它解決,以免重複計數。

發現每個數列,通過重新排列,一定能轉化為如下兩種形式之一:

  • \(1,(2,1),(2,1),\dots,2,(3,2),(3,2),\dots,3,(4,3),(4,3),\dots,j\)
  • \(1,(2,1),(2,1),\dots,2,(3,2),(3,2),\dots,3,(4,3),(4,3),\dots,j,j-1\)

其中\(j\)是序列裡最大的數。

至於為什麼所有數列一定能轉化成這樣,你只需要在知道每種值的出現次數以後,讓字典序儘可能小,就一定會排成這個形式。

通過我打的括號,大家不難看出,如果把每種值第一次出現的位置拎出來,它們一定是一個恰為\(1,2,\dots ,j\)的子序列(也就是序列裡沒有括號的部分)。而其它的位置,就是在這個子序列中,插入了若干個二元組。如果知道了序列長度\(i\)和最大元素\(j\),則這樣的序列數量,就相當於把\(\lfloor\frac{i-j}{2}\rfloor\)個元素,放入\(j-1\)個本質不同的盒子,盒子可以為空,方案數是\({\lfloor\frac{i-j}{2}\rfloor+j-1-1\choose j-1-1}\)

注:把\(x\)個本質相同的物品放入\(y\)個本質不同的盒子,盒子可以為空。

方案數可以用插板法求出。先在每個盒子裡補一個物品,讓所有盒子都不為空。然後插板,方案數是:\({x+y-1\choose y-1}\)

於是可以得到一個複雜度\(O(nm)\)的做法:列舉\(i,j\),則答案是:

\[\text{ans}=1+\sum_{i=2}^{n}\sum_{j=2}^{\min(m,i)}{\lfloor\frac{i-j}{2}\rfloor+j-2\choose j-2} \]

其中最前面的\(1\),就是隻有一個元素的序列\(\{1\}\),為了避免組合數中出現負數,我們將其單獨計算。

發現這個式子裡,\(i,j\)糾纏在一起,很難直接計算。考慮列舉\(k=\lfloor\frac{i-j}{2}\rfloor\)

的值。則答案又可以寫成:

\[\begin{align} \text{ans}&=1+\sum_{j=2}^{\min(m,n)}\sum_{k=0}^{\lfloor\frac{n-j}{2}\rfloor}{k+j-2\choose j-2} +\sum_{j=2}^{\min(m,n-1)}\sum_{k=0}^{\lfloor\frac{n-j-1}{2}\rfloor}{k+j-2\choose j-2}\\ &=1+\sum_{j=0}^{\min(m-2,n-2)}\sum_{k=j}^{\lfloor\frac{n-j-2}{2}\rfloor+j}{k\choose j} +\sum_{j=0}^{\min(m-2,n-3)}\sum_{k=j}^{\lfloor\frac{n-j-3}{2}\rfloor+j}{k\choose j}\\ &=1+\sum_{j=0}^{\min(m-2,n-2)}{\lfloor\frac{n-j-2}{2}\rfloor+j+1\choose j+1}+\sum_{j=0}^{\min(m-2,n-3)}{\lfloor\frac{n-j-3}{2}\rfloor+j+1\choose j+1} \end{align} \]

其中最後一步,是用到了\(\sum_{i=x}^{y}{i\choose x}={y+1\choose x+1}\)。可以用楊輝三角形來理解:相當於求楊輝三角上連續若干行,每行的第\(x\)個數的和(畫出來是一條斜向左下的斜線)。從上到下把每兩個數合併為下一行的第\(x+1\)個數即可。也可以用組合意義來理解:從\(y+1\)個數裡選\(x+1\)個數,列舉最後一個數在哪,然後在前面隨便選。

通過預處理階乘和逆元,求組合數是\(O(1)\)的,因此最後的時間複雜度為\(O(m)\)

參考程式碼:

//problem:BZOJ4402
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

template<typename T>inline void ckmax(T& x,T y){x=(y>x?y:x);}
template<typename T>inline void ckmin(T& x,T y){x=(y<x?y:x);}

const int MAXN=2e6;
const int MOD=1e9+7;
inline int mod1(int x){return x<MOD?x:x-MOD;}
inline int mod2(int x){return x<0?x+MOD:x;}
inline void add(int& x,int y){x=mod1(x+y);}
inline void sub(int& x,int y){x=mod2(x-y);}
inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}

int fac[MAXN+5],ifac[MAXN+5];
inline int comb(int n,int k){
	if(n<k)return 0;
	return (ll)fac[n]*ifac[k]%MOD*ifac[n-k]%MOD;
}
void facinit(int lim=MAXN){
	fac[0]=1;
	for(int i=1;i<=lim;++i)fac[i]=(ll)fac[i-1]*i%MOD;
	ifac[lim]=pow_mod(fac[lim],MOD-2);
	for(int i=lim-1;i>=0;--i)ifac[i]=(ll)ifac[i+1]*(i+1)%MOD;
}

int main() {
	facinit();
	int n,m;
	cin>>n>>m;
	int ans=1;
	/*
	for(int i=1;i<=n;++i){
		for(int j=2;j<=m && j<=i;++j){
			add(ans, comb((i-j)/2+j-1-1, j-1-1)); // (i-j)/2個東西,放進j-1個盒子,可以為空
		}
	}
	*/
	for(int j=0;j<=min(m-2,n-2);++j){
		add(ans, comb((n-j-2)/2+j+1, j+1));
	}
	for(int j=0;j<=min(m-2,n-3);++j){
		add(ans, comb((n-j-3)/2+j+1, j+1));
	}
	cout<<ans<<endl;
	return 0;
}