首页 > Web开发 > 详细

P4208 [JSOI2008]最小生成树计数

时间:2019-07-18 01:29:01      阅读:96      评论:0      收藏:0      [点我收藏+]

先写了个垃圾版本。。。加强版先咕着


思路:最小生成树的性质?+无脑搜索or矩阵树定理

提交:4次(反思反思)

题解:

简单版:

首先显然最小生成树相同权值的边的数量是不变的(否则就不是最小生成树);

然后就是相同权值的边组成的连通块的状态是不变的(就是不管你从权值为$w$的边中选出哪几条,只要合法,连通块的状态都是一样的),即不同权值的边是互不干扰的。

简单证明:(from 邓大佬)

技术分享图片

$tql$

然后我们就可以先造出一颗最小生成树,记录每种权值出现的次数,然后对于相同权值的边直接搜索用哪几条,用非路径压缩的并查集维护连通性(便于恢复连通块状态),把答案乘起来取模,ok。

代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#define R register int
using namespace std;
#define ull unsigned long long
#define ll long long
#define pause (for(R i=1;i<=10000000000;++i))
#define In freopen("NOIPAK++.in","r",stdin)
#define Out freopen("out.out","w",stdout)
namespace Fread {
static char B[1<<15],*S=B,*D=B;
#ifndef JACK
#define getchar() (S==D&&(D=(S=B)+fread(B,1,1<<15,stdin),S==D)?EOF:*S++)
#endif
inline int g() {
    R ret=0,fix=1; register char ch; while(!isdigit(ch=getchar())) fix=ch==-?-1:fix;
    if(ch==EOF) return EOF; do ret=ret*10+(ch^48); while(isdigit(ch=getchar())); return ret*fix;
} inline bool isempty(const char& ch) {return (ch<=36||ch>=127);}
inline void gs(char* s) {
    register char ch; while(isempty(ch=getchar()));
    do *s++=ch; while(!isempty(ch=getchar()));
}
} using Fread::g; using Fread::gs;
namespace Luitaryi {
const int N=110,M=1010,mod=31011;
int n,m,i,cnt,tot;
ll ans=1;
int fa[N],l[M],r[M],c[M];
struct edge { int u,v,w; edge() {}
    edge(int uu,int vv,int ww) {u=uu,v=vv,w=ww;}
    inline bool operator < (const edge& that) const {return w<that.w;}
}e[M];
inline int getf(int x) {return x==fa[x]?x:getf(fa[x]);}
inline void dfs(int lst,int s) {
    if(lst==r[i]+1) {tot+=s==c[i]; return;}
    R u=e[lst].u,v=e[lst].v;
    R uf=getf(u),vf=getf(v);
    if(uf!=vf)
        fa[uf]=vf,dfs(lst+1,s+1),
        fa[uf]=uf,fa[vf]=vf;
    dfs(lst+1,s);
}
inline void main() {
    n=g(),m=g(); for(R i=1,u,v,w;i<=m;++i) u=g(),v=g(),w=g(),e[i]=edge(u,v,w);
    sort(e+1,e+m+1); for(R i=1;i<=n;++i) fa[i]=i;
    for(i=1;i<=m&&tot<n-1;++i) {
        if(e[i].w!=e[i-1].w) ++cnt,r[cnt-1]=i-1,l[cnt]=i;
        R u=e[i].u,v=e[i].v;
        R uf=getf(u),vf=getf(v);
        if(uf!=vf) ++tot,++c[cnt],fa[uf]=vf;
    } --i; while(i<m&&e[i+1].w==e[i].w) ++i; r[cnt]=i;
    if(tot<n-1) return (void)printf("0\n");
    for(R i=1;i<=n;++i) fa[i]=i;
    for(i=1;i<=cnt;++i) {
        tot=0; dfs(l[i],0); ans=ans*tot%mod;
        for(R j=l[i];j<=r[i];++j) fa[getf(e[j].u)]=getf(e[j].v);
    } printf("%lld\n",ans);
}
}
signed main() {
    Luitaryi::main();
}

复杂版:

发现:

1.每种权值的边的数量是固定的。

2.不同的生成树中,某一种权值的边任意加入需要的数量后,形成的联通块状态是一样的。

这样一来,我们可以枚举树边的权值$i$,把权值不是$i$的树边都加入图中后进行缩点;对于权值为$i$ 的原图中的边,在缩点后的图中构造基尔霍夫矩阵,用矩阵树定理求出方案数。

代码先咕着$qwq$


2019.07.17

 

P4208 [JSOI2008]最小生成树计数

原文:https://www.cnblogs.com/Jackpei/p/11204385.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!