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(化简)

点击查看代码
void Solve() {
    LL 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 成文


posted @ 2022-01-15 22:12  hh2048  阅读(83)  评论(0)    收藏  举报