1. 程式人生 > 其它 >CF1569C - Jury Meeting(數論+排列組合 / 提高階)

CF1569C - Jury Meeting(數論+排列組合 / 提高階)

【2021.09.08】比賽第三題,【2022.01.15】補題

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 成文