【组合数】2021ICPC济南补题-C Optimal Strategy

题目链接

Description

Ena and Mizuki are playing a game.

There are n items in front of them, numbered from 1 1 1 to n n n. The value of the i i i-th item is a i a_i ai. Ena and Mizuki take turns to move, while Ena moves first. In a move, the player chooses an item that has not been taken and takes it away. The game ends when all items are taken away. The goal of either player is to maximize the sum of values of items they have taken away.

Given that both players move optimally, how many possible game processes are there? Since the number may be too large, you should output it modulo 998244353 998244353 998244353.

Two processes are considered different if there exists some integer i ( 1 ≤ i ≤ n ) i(1≤i≤n) i(1in) such that the indices of items taken away in the i i i-th move are different.

Input

The first line contains an integer n ( 1 ≤ n ≤ 1 0 6 ) n (1≤n≤10^6) n(1n106).

The second line contains n integers a 1 , a 2 , … , a n ( 1 ≤ a i ≤ n ) a_1,a_2,…,a_n (1≤a_i≤n) a1,a2,,an(1ain).

Output

Output the answer.

Sample Input 1

3
1 2 2

Sample Output 1

4

Sample Input 2

6
1 3 2 2 3 1

Sample Output 2

120

Sample Input 3

12
1 1 4 5 1 4 1 9 1 9 8 10

Sample Output 3

28800

Explanations

In the first example, there are four possible processes:

  • [ 1 , 2 , 3 ] [1,2,3] [1,2,3].
  • [ 1 , 3 , 2 ] [1,3,2] [1,3,2].
  • [ 2 , 3 , 1 ] [2,3,1] [2,3,1].
  • [ 3 , 2 , 1 ] [3,2,1] [3,2,1].

Here [ a , b , c ] [a,b,c] [a,b,c] means that in the first move Ena takes away the a a a-th item, in the second move Mizuki takes away the b b b-th item, and in the final move Ena takes away the c c c-th item.

Note that [ 2 , 1 , 3 ] [2,1,3] [2,1,3] is not a possible process, since the second move is not optimal for Mizuki.

题意

给定n个物品和其价值(数值可以重复),Ena和Mizuki轮流取物品,每个人的得分是其所取物品的价值总和,Ena先手,每个人取的都是当前的最优,问到过程结束(结果不变)中间有多少种取法的过程。

思路

(直通车:官方的一句话题解,考虑最大值如果是偶数个,那么会每次被两个两个的取;如果是奇数个,那么会被先手立刻取走一个,变成偶数的情况。)

(赛中很痛苦,思路特别杂乱,本次记录思考过程也是反思)

首先知道 a 1 a_1 a1 a n a_n an的数值,就相当于已经知道了结果,并且结果一定是先手赢或者是平局(如果后手有赢的策略,先手一定会先拿),知道这个并没有什么用处。

由于下标不同,数值相同的数取来也能算一种不同方案,这里可以直接按照同数值的个数计数,用 c n t [ i ] cnt[i] cnt[i]表示数值 i i i出现的次数。设 m a x v a l maxval maxval为当前所剩数中的最大值, m i n v a l minval minval为当前所剩数中的最小值。

怎么想到和奇偶性质有关呢?

先不管样例1里面先手知道自己必胜所以先取相对小的数也不影响结果的操作,就用贪心的思路想一想。对过程分析一步步分析,如果 m a x v a l maxval maxval是奇数个,若要符合最优策略,先手必定拿走第一个和剩下的一半,若是偶数个,两人肯定对半分,这对所有的剩下的数都适用,所以奇数情况的下一步就是偶数情况,这里可以想到和数值个数有关系了,所以最优取法的过程中,每个数值一定是两个两个配对着取。

对取法过程进行分析,先不贪心,因为从大到小考虑很难分析单个过程(在这里被卡死了),不如从小的值到大的值考虑。

先手取 m i n v a l minval minval,后手取剩下的数中的 m i n v a l minval minval的话。

如果上一步对家没有拿当前的 m i n v a l minval minval,说明当前的 m i n v a l minval minval在自己这里所有的数中一定是最大,或者是和最大值相等的,所以 m i n v a l minval minval可以放在当前的所取的数的策略,前面的任意一步取得。并且,由于从小到大遍历,现在自己取了 m i n v a l minval minval,前面的数是都不符合贪心策略的,因为它们都比 m i n v a l minval minval小,只有后面的数要符合贪心,所以前面的数在前面的过程中可以按照任意顺序取。

取法过程分析完了,求一个公式。

在当前最大值是 m a x v a l maxval maxval时,取 m a x v a l maxval maxval的过程会有 c n t [ m a x v a l ] ! cnt[maxval]! cnt[maxval]!种,因为下标不同取来也算一种不同的取法

由于要符合最优策略,当前的 m a x v a l maxval maxval一定是贪来保证个数到 ⌊ c n t [ m a x v a l ] / 2 ⌋ \lfloor cnt[maxval]/2\rfloor cnt[maxval]/2

但是自己取的数不一定只有 m a x v a l maxval maxval一种,并且不用保证最先取到 m a x v a l maxval maxval,只要保证 m a x v a l maxval maxval的个数,前面小于 m a x v a l maxval maxval的值可以随便拿

所以可以得知,数值由小到大遍历的过程中自身的取法个数:

i i i为当前 m a x v a l maxval maxval的值,前面小于 i − 1 i-1 i1的数都取到了,保证 i i i的个数是取到了 ⌊ c n t [ i ] / 2 ⌋ \lfloor cnt[i]/2\rfloor cnt[i]/2个,过程随便拿,所以这些策略取法的总和是 C ∑ j = 1 i − 1 c j + ⌊ c i / 2 ⌋ ⌊ c i / 2 ⌋ C_{\sum_{j=1}^{i-1}c_j+\lfloor c_i/2\rfloor }^{\lfloor c_i/2 \rfloor} Cj=1i1cj+ci/2ci/2

所以最后答案是: ∑ i = 1 n c n t i ! C ∑ j = 1 i − 1 c n t j + ⌊ c n t i / 2 ⌋ ⌊ c n t i / 2 ⌋ \sum_{i=1}^n cnt_i!C_{\sum_{j=1}^{i-1}cnt_j+\lfloor cnt_i/2\rfloor }^{\lfloor cnt_i/2 \rfloor} i=1ncnti!Cj=1i1cntj+cnti/2cnti/2

优化:组合数,组合数的逆元需要预处理, c n t [ j ] cnt[j] cnt[j]的求和可以用前缀和优化,细节问题就是取模之类。

复杂度 O ( n ) O(n) O(n)

AC代码

#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define llinf 0x3f3f3f3f3f3f3f3f
#define int long long
#define ull unsigned long long
#define PII pair<int,int>
#define endl '\n'
const int N = 1e6 + 10;
const int mod = 998244353;
const double pi = acos(-1.0);
typedef long long ll;
using namespace std;
int t, n;
int fact[N];
int invfact[N];
int presum[N];
int a[N];
int cnt[N];
int quickpow(int a, int b) {//快速幂
	int res = 1;
	while (b > 0) {
		if (b & 1) res = res * a % mod;
		b >>= 1;
		a = a * a % mod;
	}
	return res;
}
int getinv(int a) { return quickpow(a, mod - 2); }//费马小定理求逆元
void init() {//预处理阶乘和阶乘的逆元
	fact[0] = 1;
	for (int i = 1; i < N; i++) {
		fact[i] = fact[i - 1] * i % mod;
	}
	invfact[0] = 1;
	invfact[N - 1] = quickpow(fact[N - 1], mod - 2);
	for (int i = N - 2; i > 0; --i) {
		invfact[i] = invfact[i + 1] * (i + 1) % mod;
	}
	return;
}
int C(int n, int m) {//组合数
	return fact[n] * invfact[m] % mod * invfact[n - m] % mod; 
}
void solve() {
	for (int i = 0; i <= n; i++) {//初始化
		a[i] = 0;
		cnt[i] = 0;
	}
	for (int i = 1; i <= n; i++) {//输入
		cin >> a[i];
		cnt[a[i]] ++;
	}
	presum[1] = cnt[1];
	for (int i = 2; i <= n; i++) {//cnt[i]的前缀和
		presum[i] = presum[i - 1] + cnt[i];
	}
	int sum = 1;
	for (int i = 1; i <= n; i++) {//按公式求和
		sum = sum * fact[cnt[i]] % mod * C(presum[i - 1] - presum[0] + cnt[i] / 2, cnt[i] / 2) % mod;
	}
	cout << (sum) % mod << endl;//输出
	return;
}
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	init();
	while (cin >> n) {
		solve();
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值