「題解」清華集訓 2016 你的生命已如風中殘燭


本文將同步發布於:

題目

題目鏈接:洛谷 P6672UOJ 273

題意簡述

給你牌數為 \(m+1\) 的牌堆,其中第 \(m+1\) 張為固定的牌,每張牌用一個非負整數 \(\omega\) 表示,表示打出這張牌可以繼續抽 \(\omega\) 張牌。

給出所有 \(\omega\geq 1\) 的牌的數量 \(n\)\(\omega_i\),請你求出在總共 \(m!\) 種情況中,有多少種情況可以抽到最后一張牌(固定的牌)。

題解

數學轉化

考慮用數學語言表示題目,即轉化合法牌堆需要滿足的條件。

定義 \(\texttt{sum}_i=\sum\limits^{i}_{j=1}\omega_j\),那么我們可以轉化題意條件為

\[\forall i\in[1,m],\texttt{sum}_i-i\geq 0 \]

意思就是,我在打出第 \(i\) 張特殊牌之后收集的牌的總數量要大於等於打出的數量,這樣才合法。

考慮重新定義 \(\texttt{sum}_i=\sum\limits^{i}_{j=1}(\omega_j-1)\),那么我們可以轉化題意條件為

\[\forall i\in[1,m],\texttt{sum}_i\geq 0 \]

也就是說,我們將 \(\omega_i-1\) 看作一個新的數列,原數列合法當且僅當新數列的前綴和均為非負整數。

激發聯想

一個整數數列的前綴和可以激發我們的聯想,使得我們聯想到 Raney 引理。

Raney 引理:設整數序列 \(A=\{a_1,a_2,\cdots,a_n\}\),且部分和 \(S_k=a_1+\cdots+a_k\),序列中所有的數字的和 \(S_n=1\),則在 \(A\)\(n\) 個循環表示中,有且僅有一個序列 \(B\),滿足 \(B\) 的任意部分和 \(S_i\) 均大於零。

我們先來證明一下 Raney 引理。

考慮對任意一個整數數列做出前綴和圖像。

下面以 \(a=[1,3,-4,1]\) 為例。

作一條斜率為 \(\frac{1}{n}\) 的直線,將其平移到與圖像下相切。

raney.png

  • 充分性:不難發現,如果我們以切點為循環位移的終點(它后一個點為數列的第一項),構造出來的數列一定符合條件;
  • 必要性:如果不相切,必定存在其他的交點,考慮到數列中都是整數,交點一定滿足縱坐標小於等於切點,做差后等價於前綴和小於等於零,不合法。

Raney 引理得證。

進一步地轉化

現在的問題是我們將 \(\omega_i-1\) 看作一個新的數列,原數列合法當且僅當新數列的前綴和均為非負整數。這與 Raney 引理的形式很不一樣。

我們發現當前的數列有幾個特征:

  • 總和為 \(0\),但 Raney 引理要求為 \(1\)
  • 我們要求前綴和大於等於 \(0\),但 Raney 引理要求大於 \(0\)

冷靜思考可以構造出一種優雅的轉化方式:給數列末端加上一個 \(-1\)。然后整個數列倒過來,所有數符號取反。

這樣就完成了上述兩個特征的轉化,在這一步轉化中,我們將數列的和轉成了 \(1\),並且前綴和必須大於 \(0\)

前綴和轉化的具體過程:

  • 前綴和大於等於 \(0\),且數列總和為 \(0\),說明數列的后綴和小於等於 \(0\)
  • 在數列末端插入 \(-1\),新數列的后綴和小於等於 \(-1\)
  • 倒轉數列,新數列的前綴和小於等於 \(-1\)
  • 符號取反,不等號方向改變,新數列的前綴和大於等於 \(1\),即大於 \(0\)

組合數學求答案

Raney 引理指引我們,合法的方案數等於新數列原排列的方案數,這是因為一個圓排列恰好對應一組循環同構的排列。

所以我們只需要求出經過上面一系列轉化后的數列的圓排列個數即可,答案為 \(\frac{(m+1)!}{m+1}=m!\)

可是這樣對嗎?不對。

因為我們在數列中插入了一個 \(-1\)(后變為 \(1\)),而根據推理,原數列中 \(\omega_i=0\) 的數列在新數列中的值也為 \(1\),它們之間的排列重復計算,因此應該用除法原理去除。

原來共有 \(m-n\)\(0\),對應排列數為 \((m-n)!\),而新數列中對應 \(1\) 的個數為 \(m-n+1\),所以答案應當除以 \(m-n+1\)

總而言之,答案為 \(\frac{m!}{m-n+1}\)

參考程序

#include<bits/stdc++.h>
using namespace std;
#define reg register
typedef long long ll;

const int mod=998244353;

int n;

int main(void){
	scanf("%d",&n);
	reg int m=0;
	for(reg int i=1;i<=n;++i){
		static int w;
		scanf("%d",&w);
		m+=w;
	}
	reg int ans=1;
	for(reg int i=1;i<=m;++i)
		if(m-n+1!=i)
			ans=1ll*ans*i%mod;
	printf("%d\n",ans);
	return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM