CF838C Future Failure

37 篇文章 0 订阅
16 篇文章 0 订阅

初见安~这里是传送门:CF838C Future Failure

题解

博弈思路,可以发现当前是否要减少一个字符取决于当前的排列数量奇偶性。

(若当前有k种字符,各有a_k个,共n个,那么排列数量就是:\frac{n!}{\prod_{i=1}^k a_i!}。)

可以发现,若当前状态有偶数种排列,那么我先手则最后一定是对方被迫删掉一个字符;反之奇数则是我被迫删去一个字符。

也就是说偶数的情况下,如果下一层后存在先手必败策略那么我可以主动删掉一个字符;若全是先手必胜策略那我可以消耗到对方被迫删除一个字符,对我就是必胜。

所以当前偶数种排列的话,一定先手必胜,反之必败

若当前先手被迫需要进行一次删除操作,则相当于当前方案数为奇数,且方案数变为\frac{n!}{\prod_{i=1}^k a_i!}*\frac{a_x}{n}

因为要尽量营造下一个状态为先手必败,所以一定选的是质因数分解后2的次数最小的那个a_x让最后的奇偶性尽量为奇,换言之方案数中质因数分解后2的幂次不会增加。

这样的话,若当前n为奇数,则一定存在一个同样为奇数的a_x使操作后2的幂次不变,方案数依旧是奇数。这就是一个真正的先手必败的状态。

所以若n为奇数,则一定先手必胜。

若n为偶数,则当前排列数为偶数才先手必胜

所以现在的问题就是,n为偶数时,有多少种方案下排列数为偶数。

为偶数的条件是n!质因数分解后的2的个数大于每个ai!质因数分解后2的个数的和,所以不好判断,我们反过来算为奇数的排列数,也就是刚好等于

\lfloor \frac{a_i}{2^x} \rfloor +\lfloor \frac{a_j}{2^x} \rfloor \leq \lfloor \frac{a_i+a_j}{2^x} \rfloor的限制下,我们真正的限制条件就是:

\sum_{i=1}^k\sum_{j=1}^ {\inf}\lfloor \frac{a_i}{2^j} \rfloor=\sum_{j=1}^{\inf}\lfloor \frac{n}{2^j} \rfloor

可以想到把n和ai都换成二进制表示,这样的话就是n二进制下每个为1的位都需要唯一一个ai在那一位为1,且n为0的二进制位a也必须为0

正确性的话,因为\sum a_i=n,结合上文小于等于的限制,能取等当且仅当a_i+a_j后没有进位,也就是没有出现新的二进制位为1

至此我们已经可以dp了。因为每种选择都会对应一个方案数的计算,形如n!\sum\prod\frac{1}{a_x!}

所以——设dp_{i,j}表示前i种颜色,当前n剩余j的方案数的和。(n!最后算)

枚举当前颜色,枚举当前剩余的状态,枚举该状态的子集,就可以dp了。

记得最后计算答案的时候选的x个颜色会带一个组合数系数。

上代码——

#include<algorithm>
#include<iostream>
#include<cstring>
#include<vector>
#include<cstdio>
#include<cmath>
#include<queue>
#define maxn 300005
using namespace std;
typedef long long ll;
const int mx = 3e5;
int read() {
	int x = 0, f = 1, ch = getchar();
	while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
	while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
	return x * f;
}

int n, k, mod;
int pw(int a, int b) {int res = 1; while(b) {if(b & 1) res = 1ll * res * a % mod; a = 1ll * a * a % mod, b >>= 1;} return res;}
int fac[maxn], inv[maxn], dp[30][maxn];
int C(int n, int m) {return 1ll * fac[n] * inv[m] % mod * inv[n - m] % mod;}
signed main() {
	n = read(), k = read(), mod = read();
	
	fac[0] = inv[0] = 1;
	for(int i = 1; i <= mx; i++) fac[i] = 1ll * fac[i - 1] * i % mod;
	inv[mx] = pw(fac[mx], mod - 2);
	for(int i = mx - 1; i; i--) inv[i] = 1ll * inv[i + 1] * (i + 1) % mod;
	
	if(n & 1) printf("%d\n", pw(k, n));
	else {
		dp[0][n] = 1;
		for(int i = 1; i <= k; i++) for(int s = n; s; s = (s - 1 & n)) if(dp[i - 1][s])
			for(int j = s; j; j = (j - 1 & s)) //枚举子集
				dp[i][s - j] = (1ll * dp[i][s - j] + 1ll * inv[j] * dp[i - 1][s] % mod) % mod;
		
		ll ans = pw(k, n);
		for(int i = 1; i <= k; i++) ans = (ans - 1ll * dp[i][0] * fac[n] % mod * C(k, i) % mod + mod) % mod;
		printf("%lld\n", ans);
	}
	return 0;
}

迎评:)
——End——

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值