【题解】[FJOI2007] 洛谷P2144 轮状病毒 [三种做法&90分代码](dp | 基尔霍夫矩阵)

0.我自己瞎想

发现图可以分为外环内叉,外环又能分成几组,分组必须有一个叉。

那就分类讨论,当叉为 0,1,2,3......

使用组合数,先枚举环分组,再乘上每个环的点数即可。

看着好像没问题,但分完叉之后还得给环分组,数量级又得乘 n。

复杂度是 O(N*N*N),高精度还得乘个 100。

寄。

1.找规律

多亏瞎想大法,我们至少把1到5的数据算出来:

1,5,16,45,121......

注意到奇数项为平方数,偶数项为平方数减4。

所对应的平方数根号下为:

1,2,4,7,11......

神秘斐波那契数列。

那就有公式了:

f(i)=f(i-1)+f(i+1)

F(i)=f(i)*f(i)-(1- i\mod2)*4

搞定。

2.递推

主播,找规律还是太不严谨了,有没有更规范的做法?

有的有的,我们可以递推

既然递推,那拆环为链

枚举 ijj 比 i 小,1 到 i-j 已计算过,而 i-j+1i 是单独一组。

那么有公式:

 F(i)=\sum_{j=1}^{i}F(i-j)*j 

为啥后面乘 j 呢,因为长度为 j-1 的环上有 j 个点可以被叉选到。

链的搞定了,那环我们只需要将每种情况逆时针旋转

能旋转多少次呢?当然是第一段环的边数。

(啊这个不解释了自己画下图就知道)

那咋算呢?

这简单,上面枚举当 i=j 时,答案累计 i*i

原本的累计 i 含义是:这一段有 i 个点,可以有 i 个叉的选择。

现在的累计 i*i 含义是:和上面一样,然后整段环可以旋转 i-1 次,相当于 i+(i-1)*i

那么有公式:

 F(i)=\sum_{j=1}^{i-1} F(i-j)*j+i*i 

复杂度 O(N*N),再乘个高精度100。

还有没有更快的做法呢?当然。

这里简述下:

断环为链都一样,但在连 1——n 这条边的时候枚举。

惊喜的发现结果可以和前面公式结合:

 ans=\sum_{i=1}^{n} \sum_{j=1}^{n}F(n-j)*j 

(啊就是分别枚举第一段环的点数然后乘法原理,不过多解释)

简缩下:

ans=\sum_{i=1}^{n}F(n-i)*i*i 

(其实就是统计 F(i-j)*j 出现的次数)

复杂度O(N),乘个高精度100。

3.矩阵树 / 基尔霍夫矩阵

主播,递推固然好,但推公式麻烦怎么办?

我们还有暴力矩阵树

防止有同学还没学过线代,这里简单介绍下:

矩阵:数的集合,一般是方程的系数。

高斯消元:可以求线性方程的,也能求行列式

行列式:矩阵的值,这里我们用高斯消元直接求出来。

那么基尔霍夫矩阵又是什么东西呢?

想知道有关基尔霍夫矩阵和矩阵树的更多知识点这里

不想知道只用记得它的行列式为生成树的个数

基尔霍夫矩阵:当 i=j 时,K[i][j]i 点的度数。

i!=j 时,如果两点有边相连,K[i][j]-1;反之为 0

生成树:在一个无向图连通中,能够连接所有顶点的树结构。

我们可以把轮状基看作初始边和点的集合,我们要做的就是找它的生成树的个数。

那直接高斯消元下基尔霍夫矩阵,求个行列式,搞定。

好吧,复杂度还是有点寄,但就当学了个有用且高大上的算法吧^^

这里只给第三种做法的90分代码,其他两种不是很难,同学们自编。

(高精除我实在不想打。。同学们学第三种打前两种就好或者用别的语言)

代码里有点注释,没学过高斯消元的同学不用怕。

#include<bits/stdc++.h>
using namespace std;
//我流快读快写 
template<typename T> void qread(T &x){
	x=0; int f=1; char c=getchar();
	for(; !isdigit(c); c=getchar()) if(c=='-') f=-1;
	for(; isdigit(c); c=getchar()) x=x*10+(c-'0');
	x*=f;
}
template<typename T> void qwrite(T x){
	if(x<0) printf("-"), x=-x;
	if(x>9) qwrite(x/10);
	putchar(x%10+'0');
}
//
const int N=110;
__int128 K[N][N], ans;
int n; 
void add(int x, int y){
	K[x][x]++; K[y][y]++;
	K[x][y]--; K[y][x]--;
}
void gauss(){
	//主要做的事情就是辗转相减两行,直到该列除特殊位外都为 0 
	//当然,也有浮点的做法,不过这题数据大精度问题要考虑,你忍忍不能用 
	int r=1; ans=1; //r记录特殊位的行(一般情况下 r=c 
	for(int c=1; c<=n; c++){ //枚举列 
		for(int i=r+1; i<=n; i++){ //特殊位下面的行 
			while(K[i][c]){ 
				__int128 bs=K[r][c]/K[i][c]; //用i行的消r行 
				for(int j=1; j<=n; j++) K[r][j]-=K[i][j]*bs;
				swap(K[r], K[i]); //交换 
				ans*=-1;
				//不严格证明下:
				//设 i行值为 a,r行值为 b
				//第一步操作:i:a+b r:b
				//第二步操作:i:a+b r:b-(a+b)
				//第三步操作:i:a+b+(-a) r:-a
				//将 r的 -a的符号提出来,整个行列式就乘了 -1 
			}
		}
		if(K[r][c]!=0) r++; //判断多解用的,一般都执行 
	}
	for(int i=1; i<=n; i++) ans*=K[i][i];
	//高斯消元完整个矩阵就变成上三角矩阵(主对角行列式),直接乘对角即可 
	qwrite(ans);
}
int main(){
    memset(K, 0, sizeof(K));
	qread(n);
	//建图 
	for(int i=1; i<=n; i++){
		add(i, n+1);
		if(i==n) add(n, 1);
		else add(i, i+1);
	}
	//
	gauss();
	return 0;
} 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值