zoukankan      html  css  js  c++  java
  • CF838C Future Failure

    组合数奇偶判定的 trick。一句话总结就是 (n - sum_n=k)。其中 (sum_n) 表示 (n) 在二进制下的 (1) 的个数,(k) 表示 (n!) 含有因子 (2) 的数目。


    首先如果当前串的不同排列数为偶数的话,也就是先手可以选择改变先后手的时候,先手必胜。

    如果当前串的不同排列数为奇数:如果当前字符个数为奇数则先手必胜,否则先手必败。证明:设 (n) 为当前串长度,容易得到不同排列数为 (p=frac{n!}{prod a_i!}),其中 (a_i) 表示某种字符的个数。每次拿走一个字符相当于让 (p) 乘上一个 (frac{a_i}n),那么我选取一个 (a_i) 使得 (a_i!) 含因子 (2) 的个数最少,容易发现这样选以后不会改变 (p) 的奇偶性,那么 (p) 始终为奇数,上述结论显然。

    现在我们知道如何判断一个排列的胜负态了,准备考虑计数。根据最上面的那个 trick 我们知道 (p) 是奇数当且仅当 (n - sum_n=sum(a_i-sum_{a_i})),其中 (sum_x) 表示 (x) 在二进制下 (1) 的个数。又因为 (n=sum a_i),所以我们知道 (sum_n=sum sum_{a_i})。显然还必定有 (n=a_1igotimes a_2 igotimes cdots igotimes a_k),其中 (igotimes) 表示异或操作,也就是 (n) 分解成二进制以后每个 (1) 被分到了一个且仅有一个 (a_i) 中。

    然后就可以直接 DP 了:设 (f_{i,s}) 表示到第 (i) 个数,(n) 还剩 (s) 的方案数。转移非常显然,但是时间可能会超过,这里我们进行一个优化:强制每次必须选 (lowbit(n))(n) 在二进制下最低位),然后就可以通过此题了。

    代码:

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    
    using namespace std;
    
    const int N = 250009, K = 250000;
    int n, k, M, f[29][N], fac[N], inv_fac[N];
    
    int ksm(int a, int b)
    {
    	int res = 1;
    	while (b)
    	{
    		if (b & 1)
    			res = 1ll * res * a % M;
    		b >>= 1, a = 1ll * a * a % M;
    	}
    	return res;
    }
    
    void init()
    {
    	scanf("%d %d %d", &n, &k, &M);
    	fac[0] = 1;
    	for (int i = 1; i <= K; i++)
    		fac[i] = 1ll * fac[i - 1] * i % M;
    	inv_fac[K] = ksm(fac[K], M - 2);
    	for (int i = K - 1; i >= 0; i--)
    		inv_fac[i] = 1ll * inv_fac[i + 1] * (i + 1) % M;
    }
    
    int lowbit(int x) { return x & (-x); }
    
    int solve(int cur, int S)
    {
    	if (f[cur][S] != -1) return f[cur][S];
    	if (!S)
    	{
    		f[cur][S] = fac[n];
    		for (int i = 1; i <= cur; i++)
    			f[cur][S] = 1ll * f[cur][S] * (k - i + 1) % M;
    		return f[cur][S];
    	}
    	f[cur][S] = 0;
    	int U = S - lowbit(S);
    	for (int T = U; T; T = (T - 1) & U)
    		f[cur][S] = (f[cur][S] + 1ll * inv_fac[S - T] * solve(cur + 1, T) % M) % M;
    	f[cur][S] = (f[cur][S] + 1ll * inv_fac[S] * solve(cur + 1, 0) % M) % M;
    	return f[cur][S];
    }
    
    void work()
    {
    	if (n & 1)
    		printf("%d
    ", ksm(k, n));
    	else
    	{
    		memset(f, -1, sizeof(f));
    		int res = (ksm(k ,n) - solve(0, n) + M) % M;
    		printf("%d
    ", res);
    	}
    }
    
    int main()
    {
    	init();
    	work();
    	return 0;
    }
    
    
  • 相关阅读:
    LOJ.6435.[PKUSC2018]星际穿越(倍增)
    webpack---style-loader的配置:insertAt 和insert
    react-native项目启动报错——watchman安装问题(mac pro)
    js基础---event.target/ event.currentTarget/this的区别
    js基础---querySelector系列和getElementsBy系列获取页面元素的最大差异(返回值的属性区别)
    js基础----用户在浏览器输入网址后页面的加载
    js基础----dom节点使用console.log打印始终是最新的现象(待验证)
    js基础---嵌套循环中的break使用
    环境变量的配置之——全局安装@vue/cli脚手架,出现vue不是内部或外部命令(Windows)
    Chocolatey 和 Scoop的安装和介绍 (windows)
  • 原文地址:https://www.cnblogs.com/With-penguin/p/13909312.html
Copyright © 2011-2022 走看看