【LOJ6622】[THUPC2019] 找树(FWT+矩阵树定理)

点此看题面

大致题意: 定义\(a\oplus b\)的第\(i\)位为\(a\)的第\(i\)位与\(b\)的第\(i\)位进行给定位运算\(\oplus_i\)(与/或/异或)的结果。给定一张无向图,每条边的边权是一个\(w\)位二进制数,让你找到一个生成树,求出所有边权进行\(\oplus\)运算的结果的最大值。

广义\(FWT\)

看到这种对于不同位进行不同位运算的题目,我们要想到一个叫做广义\(FWT\)的东西。

它其实就是一般\(FWT\)三种卷积的混合版本,只要在做\(FWT\)时根据这一位上运算方式的不同采取不同的变换方式就可以了。

关于它的具体实现可见代码。

矩阵树定理

对于生成树,有一个知名算法,就是矩阵树定理

或许你会感到奇怪,矩阵树定理明明是用于做生成树计数的,而这题求的却是最大值,和计数问题似乎完全没有半点关系啊。

但实际上,这是一道披着最大化外壳的计数题,而它的转化真的是特别神仙。

转化

我们定义\(a_{i,j,v}\)表示仅考虑边权为\(v\)的边时的基尔霍夫矩阵\(i\)行第\(j\)列的值。

这东西看起来没有任何用处,但如果我们对于每一个数组\(a_{i,j}\),都做一次广义\(FWT\),然后就会发现,对于每一个\(v\),它的基尔霍夫矩阵都独立了!

于是我们利用高斯消元,求出每一个基尔霍夫矩阵的行列式的值(即生成树个数),存到一个名为\(ans\)的数组里。

接着我们再对\(ans\)\(IFWT\),则此时的\(ans_i\)实际上就是做\(\oplus\)运算所得值为\(i\)的生成树个数!

那么我们只要找到最大的一个\(i\)满足\(ans_i\not=0\),这道题就做完了。

不得不说,这道题的思路和做法真的都非常妙,是我这种蒟蒻这辈子都想不到的。。。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 70
#define K 12
#define X 998244353
#define I2 499122177
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
#define Dec(x,y) ((x-=(y))<0&&(x+=X))
#define swap(x,y) (x^=y^=x^=y)
using namespace std;
int n,m,w,P,ans[1<<K],A[N+5][N+5],a[N+5][N+5][1<<K];char f[K+5];
I int QP(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
I void FWT(int *s,CI op)//广义FWT
{
	RI t,i,j,k,x,y;for(t=i=1;i^P;++t,i<<=1) for(j=0;j^P;j+=i<<1) for(k=0;k^i;++k) switch(f[t])
	{
		case '&':~op?Inc(s[j+k],s[i+j+k]):Dec(s[j+k],s[i+j+k]);break;
		case '|':~op?Inc(s[i+j+k],s[j+k]):Dec(s[i+j+k],s[j+k]);break;
		case '^':s[j+k]=((x=s[j+k])+(y=s[i+j+k]))%X,s[i+j+k]=(x-y+X)%X,
			!~op&&(s[j+k]=1LL*s[j+k]*I2%X,s[i+j+k]=1LL*s[i+j+k]*I2%X);break;
	}
}
I bool Find(CI x)
{
	RI i=x+1,j;W(i^n&&!A[i][x]) ++i;if(i==n) return 0;
	for(j=x;j^n;++j) swap(A[x][j],A[i][j]);return 1;
}
I int Det()//高斯消元求行列式的值
{
	RI i,j,k,t,g,res=1;for(i=1;i^n;++i)
	{
		if(!A[i][i]&&!(res=X-res,Find(i))) return 0;res=1LL*res*A[i][i]%X,g=QP(A[i][i],X-2);
		for(j=i+1;j^n;++j) for(t=X-1LL*A[j][i]*g%X,k=i+1;k^n;++k) A[j][k]=(1LL*t*A[i][k]+A[j][k])%X;
	}return res;
}
int main()
{
	RI i,j,k,x,y,z;scanf("%d%d%s",&n,&m,f+1),P=1<<(w=strlen(f+1));
	for(i=1;i<=m;++i) scanf("%d%d%d",&x,&y,&z),++a[x][x][z],++a[y][y][z],--a[x][y][z],--a[y][x][z];//初始化基尔霍夫矩阵
	for(i=1;i<=n;++i) for(j=1;j<=n;++j) {for(k=0;k^P;++k) a[i][j][k]=(a[i][j][k]%X+X)%X;FWT(a[i][j],1);}//FWT
	for(RI p=0;p^P;++p) {for(i=1;i^n;++i) for(j=1;j^n;++j) A[i][j]=a[i][j][p];ans[p]=Det();}//求出并存下每个矩阵行列式的值
	for(FWT(ans,-1),i=P-1;~i;--i) if(ans[i]) break;return printf("%d\n",i),0;//IFWT,然后找到最大答案
}
posted @ 2020-06-17 17:50  TheLostWeak  阅读(19)  评论(0编辑  收藏