CF1569C - Jury Meeting(數論+排列組合 / 提高階)
1569C - Jury Meeting(源地址自⇔CF1569C)
目錄tag
⇔數論、⇔排列組合、⇔提高階(*1500)
題意
有 \(n\) 個人圍坐成一圈交流提案,每個人的提案數量通過 \(a[1...n]\) 陣列給出。所有人按照順序依次發言,發言一次這個人的提案數量便 \(-1\) ,當這個人的提案數量為 \(0\) 後他會退出討論。現在,請問有多少種可能的排列方式,使得所有人交叉發言,而不會出現一個人連續發言兩次的情況。
思路
不妨假設最大的提案數量為 \(x\) ,一共有 \(X = Num_x\) 個人的提案數量為 \(x\) 。觀察樣例並作簡單推導,我們發現,排列方法與 \(x\) 的數量直接相關——當 \(X \ge 2\) 時,無論怎麼排列都是符合要求的;當 \(X = 1\) 時,在這個人發言順序之後、下一輪開始之前,一定至少要有一個提案數為 \(x-1\) 的人發言,不妨假設次大值 \(y = x - 1\) ,一共有 \(Y\) 人。至此,題目分為三種情況:
- \(X > 1\) ,答案直接為 \(A_n^n\) ;
- \(X = 1\) ,且 \(Y = 0\) ,答案直接為 \(0\)
- \(X = 1\) ,且 \(Y \ne 0\) ,討論如下。
正向(自己做法)
將次大值全排列(\(A_{Y}^{Y}\)),唯一的最大值(\(A_1^1\) )可以插入到除了最後一位外的任意位(\(C_{Y}^1\)),其他數字(\(A_{n - Y - 1}^{n - Y - 1}\) )可以插入到任意位(球盒模型,球同盒不同、可以為空,\(C_{(n - Y - 1) + (Y + 1 + 1) - 1}^{(Y + 1) - 1} = C_{n}^{Y}\))。
至此,答案為 \(Y! * Y * (n - Y - 1)! * C_{n}^{Y}\) ,進一步化簡為 \(n! - \frac{n!}{Y + 1}\)
逆向(答案做法)
首先考慮不符合要求的情況:提案數為 \(x\) 的人最後一個發言。先將次大值全排列(\(A_{Y}^{Y}\)),唯一的最大值(\(A_1^1\) )插入到最後一位(\(C_1^1\)),再將其他的數字全排列(\(A_{n}^{n - Y - 1}\))。
至此,答案為 \(n! - A_{n}^{n - Y - 1} * Y!\) ,進一步化簡為 \(n! - \frac{n!}{Y + 1}\) 。
AC程式碼1(使用 \(\tt{}vector\) )
點選檢視程式碼
void Solve() {
int frac = 1, sub = 1;
cin >> n;
VI v(n);
for(auto &it : v) cin >> it;
int x = *max_element(v.begin(), v.end());
int X = count(v.begin(), v.end(), x);
int Y = count(v.begin(), v.end(), x - 1);
FOR(i, 1, n) {
frac = frac * i % MOD;
if(i != Y + 1) sub = sub * i % MOD;
}
if(X == 1) frac = (frac - sub + MOD) % MOD;
cout << frac << endl;
}
AC程式碼2(硬算)
點選檢視程式碼
LL C(LL n, LL m, LL p = MOD) {
if(m > n) return 0;
LL a = 1, b = 1;
FOR(i, 1, m) {
a = a * (n + i - m) % p;
b = b * i % p;
}
return a * mypow(b, p - 2, p) % p;
}
LL Lucas(LL n, LL m, LL p = MOD) {//盧卡斯定理
LL ans = 1;
while(n && m) {
ans = ans * C(n % p, m % p, p) % p;
n /= p;
m /= p;
}
return ans;
}
void Prepare(int x) {
FOR(i, 1, x) {
A[i] = A[i - 1] * i % MOD;
}
}
void Solve() {
cin >> n;
Prepare(n + 1);
FOR(i, 1, n) cin >> a[i];
sort(a + 1, a + 1 + n, [] (int x, int y) {
return x > y;
});
mm = a[1];//最大值
nummm = 0;
mn = 0;//次大值
nummn = 1;
FOR(i, 1, n) {
if(a[i] == mm) nummm ++;
else if(mn == 0) mn = a[i];
else if(a[i] == mn) nummn ++;
else break;
}
if(nummn != 0 && nummm == 1 && mm - mn != 1) { //相差只能為 1
ans = 0;
}else if(nummm != 1) { //最大值不止一個,亂排
ans = A[n];
}else if(nummm == 1) { //最大值只有一個
//盒子數量nummm + nummn + 1,球數量n - nummm - nummn。
ans = A[nummn] * nummn % MOD * Lucas(n, 1 + nummn) % MOD * A[n - 1 - nummn] % MOD;
}
cout << ans << endl;
}
錯誤次數
無
文 / WIDA
2022.01.15 成文
首發於WIDA個人部落格,僅供學習討論
更新日記:
2022.01.15 成文