[硫化铂]treecnt

treecnt

题目概述

在这里插入图片描述
在这里插入图片描述

题解

首先我们关注一下我们的每个限制,要求某一个点集在生成树上是一个连通块,看起来很奇怪。
如果直接通过连通块去扩展什么的很容易达到指数级复杂度,但我们可以考虑将这个条件做一定的转化。
首先,如果我们对于一个大小 ∣ S ∣ |S| S的集合 S S S,显然,我们最后得在这个集合的导出子图中选择 ∣ S ∣ − 1 |S|-1 S1条边以形成一棵树,这个导出子图中最多也只会被选择 ∣ S ∣ − 1 |S|-1 S1条边,当它最多时,恰好合法。
那我们不妨给每条边赋一个点权 w w w表示它出现在了多少个导出子图中,显然,我们如果最后得到生成树的所有边的权值和为 ∑ i = 1 k max ⁡ ( 0 , ∣ S i ∣ − 1 ) \sum_{i=1}^k\max(0,|S_i|-1) i=1kmax(0,Si1),那么就是合法的,而这又肯定是整张图的权值和的最大值。

所以我们相当于要做一个最大生成树计数。
这个过程我们可以通过 K r u s k a l Kruskal Kruskal实现,当我们真正需要选择的边就是能够连接边权比它大的边形成的森林中边权相同的边形成的连通块。
只有这些边的内部是有冲突的,我们得给它们决定出一个生成树,需要保证连通块是因为我们的基尔霍夫定理的实现需要在一个连通块内建树,然后高斯消元求行列式即可。
将每个连通块的行列式乘起来就可以得到我们的答案。
而算每条边的权值我们不能直接枚举,但可以用bitset优化,看两个端点共同出现的限制的数量,并一下即可。

时间复杂度 O ( n 3 + n 2 K ω ) O\left(n^3+\frac{n^2K}{\omega}\right) O(n3+ωn2K)

源码

#include<bits/stdc++.h> 
using namespace std;
typedef long long LL;
typedef pair<int,int> pii;
#define MAXN 2005
#define MAXM 250005
#define pb push_back
#define mkpr make_pair
#define fir first
#define sec second
const int mo=998244353;
template<typename _T>
void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
} 
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
int add(int x,int y,int p){return x+y<p?x+y:x+y-p;}
void Add(int &x,int y,int p){x=add(x,y,p);}
int qkpow(int a,int s,int p){int t=1;while(s){if(s&1)t=1ll*a*t%p;a=1ll*a*a%p;s>>=1;}return t;}
int n,K,tot,fa[505],totd,sta[MAXN],stak,ip[505],ans;
bitset<2005>st[MAXN];
struct ming{int u,v,w,cnt;}s[MAXM],d[MAXM];
bool cmp(ming x,ming y){return x.w>y.w;}
void makeSet(int x){for(int i=1;i<=x;i++)fa[i]=i;}
int findSet(int x){return fa[x]==x?x:fa[x]=findSet(fa[x]);}
void unionSet(int a,int b){int u=findSet(a),v=findSet(b);if(u^v)fa[u]=v;}
struct matrix{
	int c[505][505],m;
	matrix(){memset(c,0,sizeof(c));}
	int Geass(){
		int res=1;
		for(int t=1;t<m;t++){
			int k=t;for(int i=t;i<m;i++)if(c[i][t]){k=i;break;}
			if(k^t){for(int i=t;i<m;i++)swap(c[t][i],c[k][i]);res=mo-res;}
			if(!c[t][t])break;int tmp=qkpow(c[t][t],mo-2,mo);
			for(int i=t+1;i<m;i++)if(c[i][t]){
				int tp=1ll*c[i][t]*tmp%mo;
				for(int j=t;j<=m;j++)Add(c[i][j],mo-1ll*tp*c[t][j]%mo,mo);
			}
		}
		for(int i=1;i<m;i++)
			res=1ll*res*c[i][i]%mo;
		return res;
	}
	void clear(){
		for(int i=1;i<=m;i++)
			for(int j=1;j<=m;j++)
				c[i][j]=0;
		m=0;
	}
}A;
int main(){
	//freopen("treecnt.in","r",stdin);
	//freopen("treecnt.out","w",stdout);
	read(n);read(K);int summ=0;ans=1;
	for(int i=1;i<=n;i++)
		for(int j=i+1,x;j<=n;j++)
			read(x),s[++tot]=(ming){i,j,0,x};
	for(int i=1;i<=K;i++){
		int num=0;
		for(int j=1,x;j<=n;j++)
			scanf("%1d",&x),st[j][i]=x,num+=x;
		if(!num)continue;summ+=num-1;
	}
	for(int i=1;i<=tot;i++)
		s[i].w=(st[s[i].u]&st[s[i].v]).count();
	sort(s+1,s+tot+1,cmp);makeSet(n);int tp=0;
	for(int i=1,j;i<=tot;i=j+1){
		j=i;while(j<tot&&s[j+1].w==s[i].w)j++;totd=0;
		for(int k=i;k<=j;k++)
			if(findSet(s[k].u)!=findSet(s[k].v))
				d[++totd]=s[k],
				d[totd].u=findSet(s[k].u),
				d[totd].v=findSet(s[k].v);
		for(int k=i;k<=j;k++)
			if(findSet(s[k].u)^findSet(s[k].v))
				unionSet(s[k].u,s[k].v),tp+=s[k].w;
		for(int k=1;k<=j;k++)d[k].w=findSet(d[k].u);
		sort(d+1,d+totd+1,cmp);
		for(int l=1,r;l<=totd;l=r+1){
			r=l;while(r<totd&&d[r+1].w==d[l].w)r++;
			for(int k=l;k<=r;k++){
				if(!ip[d[k].u])sta[++stak]=d[k].u,ip[d[k].u]=stak;
				if(!ip[d[k].v])sta[++stak]=d[k].v,ip[d[k].v]=stak;
				Add(A.c[ip[d[k].u]][ip[d[k].u]],d[k].cnt,mo);
				Add(A.c[ip[d[k].v]][ip[d[k].v]],d[k].cnt,mo);
				Add(A.c[ip[d[k].u]][ip[d[k].v]],mo-d[k].cnt,mo);
				Add(A.c[ip[d[k].v]][ip[d[k].u]],mo-d[k].cnt,mo);
			}
			A.m=stak;ans=1ll*A.Geass()*ans%mo;A.clear();
			while(stak)ip[sta[stak--]]=0;
		}
	}
	if(tp^summ){puts("0");return 0;}
	printf("%d\n",ans);
	return 0;
}

谢谢!!!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值