zoukankan      html  css  js  c++  java
  • [算法] Lucas 定理

    定理内容

    对于给定非负整数 \(n,m\) 和质数 \(p\),将 \(n,m\) 转化为 \(p\) 进制表达,即为:

    \(n=n_kp^k+n_{k-1}p^{k-1}+\dots+n_1p+n_0\)

    \(m=m_kp^k+m_{k-1}p^{k-1}+\dots+m_1p+m_0\)

    则有恒等式:

    \(\dbinom{n}{m}\equiv\prod^k_{i=0}\dbinom{n_i}{m_i} (mod\) \(p)\)

    hdu3037 Saving Beans

    题目链接。

    题目大意

    \(n\) 个盒子,你可以拥有 \(1\)\(m\) 个小球,将这些小球放入盒子中,有多少方案,对于 \(p\) 取模。

    思路

    对于其中一个 \(m\),即有 \(n-1\) 个板,\(m\) 个球,用隔板发得到方案数为 \(\dbinom{n+m-1}{n-1}\)

    则总方案数为:\(\sum^m_{i=0}\dbinom{n+i-1}{n-1}=\sum^{n+m-1}_{i=n-1}\dbinom{i}{n-1}\)

    由边下项公式得到:\(\dbinom{n+m}{n}\)

    由于题目中 \(n\)\(m\) 较大,则用 Lucas 定理来优化组合数计算。

    Code

    #include <cstdio>
    #include <algorithm>
    using namespace std;
    const int MAXN = 1e5 + 5;
    int fac[MAXN], inv[MAXN];
    int n, m, p;
    int ksm(int x, int y) {
    	int res = 1;
    	while (y) {
    		if (y & 1) res = (1LL * res * x) % p;
    		x = (1LL * x * x) % p;
    		y >>= 1;
    	}
    	return res;
    }
    void Init() {
    	fac[0] = 1;
    	for (int i = 1; i < p; i++) fac[i] = (1LL * fac[i - 1] * i) % p;
    	inv[p - 1] = ksm(fac[p - 1], p - 2);
    	for (int i = p - 2; i >= 0; i--) inv[i] = (1LL * inv[i + 1] * (i + 1)) % p;
    }
    int C(int x, int y) {
    	if (x < y) return 0;
    	return 1LL * fac[x] * inv[x - y] % p * inv[y] % p;
    }
    void Work() {
    	scanf("%d %d %d", &n, &m, &p);
    	Init();
    	n = n + m;
    	int ans = 1;
    	for (int i = 0; i <= 31; i++) {
    		ans = 1LL * ans * C(n % p, m % p) % p;
    		n /= p;
    		m /= p;
    	}
    	printf("%d\n", ans);
    }
    int main() {
    	int T;
    	for (scanf("%d", &T); T; T--) Work();
    	return 0;
    }
    

    HDU5794 A Simple Chess

    题目链接。

    题意

    再平面直角坐标系中,从 \((1,1)\) 用马走日的规则走到 \((h,w)\) ,且有 \(n\) 个黑点不能走,每次只能向上或向右走,则共有多少种方案数。答案对 \(110119\) 取模。

    思路

    \(dp[i]\) 为从 \((1,1)\) 走到 \((x[i],y[i])\) 且不经过其他黑点的方案数。

    简化版问题

    若从点 \((0,0)\) 走到 \((a,b)\),且没有任何障碍的方案数。

    \((+1,+2)\) 走了 \(x\) 步,\((+2,+1)\) 走了 \(y\) 步,则有方程:

    \[\begin{cases} a=x+2y\\ b=y+2x \end{cases} \]

    解之得:

    \[\begin{cases} x=\frac{2b-a}{3}\\ y=\frac{2a-b}{3} \end{cases} \]

    那么答案为 \(\dbinom{x+y}{x}\),即为共有 \(x+y\) 步,选其中 \(x\) 步走 \((+2,+1)\)。注意,其中 \(x,y\) 为正整数。

    然后是这道题的状态转移。

    首先算出当前点 \(i\) 不考虑有黑点,有多少种方案,转化为简化版问题解决即可。

    然后考虑算重的部分。在这个点的左下方的一个点 \(j\) ,第一个经过的点为 \(j\) 且最终到达了 \(i\) 。那么路径就拆成了两部分,\((1,1)\)\(j\)\(j\)\(i\) ,第一部分就为 \(dp[j]\),第二部分转化为简化版问题求解。

    由于求出 \(dp[i]\) 时应先求到 \(dp[j]\),所以要对原数组排序,保证 \(i\) 的左下方的点都已经求过了。

    由于模数较小且 \(h,w\) 太大以至于无法预处理出组合数,所以用到 Lucas 定理来优化。

    Code

    为了方便设置起点为 \((0,0)\)

    #include <cstdio>
    #include <algorithm>
    using namespace std;
    #define int long long
    const int MAXN = 2e5 + 5;
    const int MOD = 110119;
    int fac[MAXN], inv[MAXN], dp[MAXN];
    int h, w, n;
    int Case = 0;
    struct Node {
    	int x, y;
    	friend bool operator < (Node x, Node y) { return x.x != y.x ? x.x < y.x : x.y < y.y; }
    };
    Node a[MAXN];
    int ksm(int x, int y) {
    	int res = 1;
    	while (y) {
    		if (y & 1) res = (1LL * res * x) % MOD;
    		x = (1LL * x * x) % MOD;
    		y >>= 1;
    	}
    	return res;
    }
    int c(int x, int y) {
    	if (x < y) return 0;
    	return 1LL * fac[x] * inv[x - y] % MOD * inv[y] % MOD;
    }
    int C(int x, int y) {
    	int res = 1;
    	for (int i = 0; i <= 20; i++) {
    		res = 1LL * res * c(x % MOD, y % MOD) % MOD;
    		x /= MOD;
    		y /= MOD;
    	}
    	return res;
    }
    void Work() {
    	n++;
    	a[n].x = h, a[n].y = w;
    	for (int i = 1; i <= n; i++) dp[i] = 0;
    	sort(a + 1, a + 1 + n);
    	for (int i = 1; i <= n; i++) {
    		int A = a[i].x;
    		int B = a[i].y;
    		if ((2 * A - B) % 3 != 0 || (2 * B - A) % 3 != 0 || 2 * A - B < 0 || 2 * B - A < 0) continue;
    		int x = (2 * B - A) / 3;
    		int y = (2 * A - B) / 3;
    		dp[i] = C(x + y, x);
    		for (int j = 1; j < i; j++) {
    			if (a[j].x > a[i].x || a[j].y > a[i].y) continue;
    			A = a[i].x - a[j].x;
    			B = a[i].y - a[j].y;
    			if ((2 * A - B) % 3 != 0 || (2 * B - A) % 3 != 0 || 2 * A - B < 0 || 2 * B - A < 0) continue;
    			x = (2 * B - A) / 3;
    			y = (2 * A - B) / 3;
    			dp[i] = (-1LL * dp[j] * C(x + y, x) % MOD + dp[i]) % MOD;
    		}
    	}
    	dp[n] = (dp[n] + MOD) % MOD;
    	printf("Case #%lld: %lld\n", ++Case, dp[n]);
    }
    signed main() {
    	fac[0] = 1;
    	for (int i = 1; i < MOD; i++) fac[i] = (1LL * fac[i - 1] * i) % MOD;
    	inv[MOD - 1] = ksm(fac[MOD - 1], MOD - 2);
    	for (int i = MOD - 2; i >= 0; i--) inv[i] = (1LL * inv[i + 1] * (i + 1)) % MOD;
    	while (scanf("%lld", &h) != EOF) {
    		scanf("%lld %lld", &w, &n);
    		h--, w--;
    		for (int i = 1; i <= n; i++) {
    			scanf("%lld %lld", &a[i].x, &a[i].y);
    			a[i].x--;
    			a[i].y--;
    		}
    		Work();
    	}
    	return 0;
    }
    
  • 相关阅读:
    玩转树莓派《二》——用python实现动画与多媒体
    pygame(一)
    玩转树莓派(一)
    pythonchallenge(七)
    springmvc定时器
    maven打包成第三方jar包且把pom依赖包打入进来
    mybatis之动态SQL
    黑马12期day01之html&css
    千万级数据表删除特定字断
    自动跳转
  • 原文地址:https://www.cnblogs.com/C202202chenkelin/p/15609711.html
Copyright © 2011-2022 走看看