• 【luogu P6657】【模板】LGV 引理(行列式)(数学)(线性代数)


    【模板】LGV 引理

    题目链接:luogu P6657

    题目大意

    给你一个二维图,然后分别有 m 个棋子,分别要从 (ai,1) 走到 (bi,n),只能从 (x,y) 走到 (x+1,y) 和 (x,y+1)。
    然后问你有多少种走法,使得走过路径上的点互不相交。

    思路

    LGV 引理:
    (w(P)) 为有向路径 (P) 上所有边权的乘积,然后 (f(a,b))(a ightarrow b) 的所有有向路径边权乘积的和。

    易得:
    (f(a,b)=sumlimits_{P:a ightarrow b}w(P))

    然后列出矩阵:
    (A=egin{bmatrix} f(a_1,b_1)&f(a_1,b_2)&cdots&f(a_1,b_n)\ f(a_2,b_1)&f(a_2,b_2)&cdots&f(a_2,b_n)\ vdots&vdots&ddots&vdots\ f(a_n,b_1)&f(a_n,b_2)&cdots&f(a_n,b_n) end{bmatrix})

    然后路径不交的方案数就是它的行列式,即:
    (sumlimits_{p}(-1)^{ au(p)}prodlimits_{i=1}^nA_{i,p_i}),其中 (p) 是一个排列,( au(p)) 指的是 (p) 中的逆序对数。

    至于为什么呢,其实大概感觉一下也可以看出,就类似于一个小小的容斥,那你两个交叉的话,就可以让中间的那个段两个人走法交换,就不相交了。
    所以就是这个样子。


    这道题:
    其实还没有那么难,两个点之间就一个路径,然后 (f(a,b)) 用个组合数就有了:(dbinom{b_j-a_i+n-1}{n-1})

    然后就搞就完事了。

    代码

    #include<cstdio>
    #include<algorithm>
    #define ll long long
    #define mo 998244353
    
    using namespace std;
    
    int T, n, m, a[101], b[101];
    ll f[101][101];
    ll jc[2000001], inv[2000001];
    
    ll ksm(ll x, ll y) {
    	ll re = 1;
    	while (y) {
    		if (y & 1) re = re * x % mo;
    		x = x * x % mo;
    		y >>= 1;
    	}
    	return re;
    }
    
    ll C(int n, int m) {
    	return jc[n] * inv[m] % mo * inv[n - m] % mo;
    }
    
    ll work() {//高斯消元解行列式
    	ll zf = 1, ans = 1, tmp;
    	
    	for (int i = 1; i <= m; i++) {
    		int k = i;
    		for (int j = i + 1; j <= m; j++)
    			if (f[j][i] > f[k][i]) k = j;
    		if (!f[k][i]) return 0;
    		if (k != i) swap(f[k], f[i]), zf = -zf;
    		for (int j = i + 1; j <= m; j++) {
    			if (f[j][i] > f[i][i]) swap(f[j], f[i]), zf = -zf;
    			while (f[j][i]) {
    				tmp = f[i][i] / f[j][i];
    				for (int k = i; k <= m; k++)
    					f[i][k] = (f[i][k] + f[j][k] * (mo - tmp) % mo) % mo;
    				swap(f[j], f[i]); zf = -zf;
    			}
    		}
    		
    		ans = ans * f[i][i] % mo;
    	}
    	
    	if (zf == -1) return (mo - ans) % mo;
    	return ans;
    }
    
    int main() {
    	jc[0] = 1;
    	for (int i = 1; i <= 2000000; i++) jc[i] = jc[i - 1] * i % mo;
    	inv[2000000] = ksm(jc[2000000], mo - 2);
    	for (int i = 2000000 - 1; i >= 0; i--) inv[i] = inv[i + 1] * (i + 1) % mo; 
    	
    	scanf("%d", &T);
    	while (T--) {
    		scanf("%d %d", &n, &m);
    		for (int i = 1; i <= m; i++) scanf("%d %d", &a[i], &b[i]);
    		
    		for (int i = 1; i <= m; i++)
    			for (int j = 1; j <= m; j++)
    				if (a[i] <= b[j]) f[i][j] = C(b[j] - a[i] + n - 1, n - 1);
    					else f[i][j] = 0;
    		
    		printf("%lld
    ", work());
    	}
    	
    	return 0;
    }
    
  • 相关阅读:
    当有触发器时,涉及触发器的列名不能再随便更改了,因为改变列名时并没有改变触发器,而使触发器不会发生作用
    PHP实现上次登录功能
    TRUNCATE 不能引发触发器
    unslider点导航不显示错误
    jquery插件中使用ajax并且获取使用插件的对象
    jquery插件函数传参错误
    jquery插件获取事件类型
    线程安全的 stack
    不要在锁的作用域之外通过指针或引用传递要保护的数据
    通过打包 accumulate 实现多线程版本的 accumulate
  • 原文地址:https://www.cnblogs.com/Sakura-TJH/p/luogu_P6657.html
Copyright © 2020-2023  润新知