【Luogu】P4208最小生成树计数(状压乱搞)

  题目链接

  最小生成树有两个性质,两个性质都知道的话这题就变成码农题了。

  1、无论最小生成树长什么样,所有权值的边的数量是不变的。比如我有棵最小生成树有两条权值为2的边四条权值为1的边,那这个图的所有最小生成树都是两条权值为2的边四条权值为1的边。

  2、无论最小生成树长什么样,把边从小到大排序,某一权值的边连完后,联通块一定是固定的。

  这就提示了我们先求一遍最小生成树,得到生成树里每个权值的边都有几条,然后枚举权值,状压当前权值选的边集,看能不能把选出来的这些边都摁进森林里去。如果能的话该权值的连接方案数就+1.

  枚举完之后,因为第二条所以我们直接把所有该权值的边的两个端点合到一个并查集里就好了。

  最后乘法原理得到答案。

  

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cctype>
#include<cstdlib>
#define maxn 10000
#define mod 31011
using namespace std;
inline long long read(){
    long long num=0,f=1;
    char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-')    f=-1;
        ch=getchar();
    }
    while(isdigit(ch)){
        num=num*10+ch-'0';
        ch=getchar();
    }
    return num*f;
}

struct Edge{
    int from,to,val;
}edge[maxn],q[maxn],d[maxn];
bool cmp(Edge a,Edge b){    return a.val<b.val;    }
int head[maxn],num;
inline void add(int from,int to,int val){
    edge[++num]=(Edge){head[from],to,val};
    head[from]=num;
}

bool vis[maxn];
int sum[maxn];
int w[maxn];
int size;
struct fus{
    int father[maxn];
    inline void clear(int n){    for(int i=1;i<=n;++i)    father[i]=i;    }
    int find(int x){
        if(father[x]!=x)    father[x]=find(father[x]);
        return father[x];
    }
    inline void unionn(int x,int y){
        x=find(x);    y=find(y);
        father[y]=x;
    }
}c,r,right;

inline int getlen(int x){
    int ans=0;
    while(x){
        if(x&1)    ans++;
        x>>=1;
    }
    return ans;
}

int ans[maxn];
int pre[maxn];

void copy(int x,int *a,int *b){    for(int i=1;i<=x;++i)    a[i]=b[i];}

int main(){
    int n=read(),m=read();
    for(int i=1;i<=m;++i)    q[i]=(Edge){read(),read(),read()};
    c.clear(n);
    sort(q+1,q+m+1,cmp);
    int cnt=0,last=0,now=0;
    for(int i=1;i<=m;++i){
        //离散化 
        now=q[i].val;
        if(q[i].val==last)    q[i].val=size;
        else                 q[i].val=++size;
        last=now;
        
        int from=q[i].from,to=q[i].to;
        if(c.find(from)==c.find(to))    continue;
        c.unionn(from,to);
        vis[q[i].val]=1;
        sum[q[i].val]++;
        cnt++;
        if(cnt==n-1)    break;
    }
    if(cnt<n-1){
        printf("0");
        return 0;
    }
    c.clear(n);
    cnt=0;
    int maxval=0;
    for(int i=1;i<=m;++i)
        if(vis[q[i].val]){
            d[++cnt]=q[i];
            w[q[i].val]++;
            maxval=max(maxval,q[i].val);
        }
    last=0;
    for(int i=1;i<=maxval;++i){
        int Max=1<<w[d[last+1].val];
        copy(n,right.father,c.father);
        for(int j=0;j<Max;++j){
            if(getlen(j)!=sum[d[last+1].val])    continue;
            bool flag=0;
            copy(n,r.father,c.father);
            for(int k=0;(1<<k)<=j;++k)
                if(j&(1<<k)){
                    int ret=(k+1)+last;
                    if(r.find(d[ret].from)==r.find(d[ret].to)){
                        flag=1;
                        break;
                    }
                    r.unionn(d[ret].from,d[ret].to);
                }
            if(flag==0){
                ans[d[last+1].val]++;
                copy(n,right.father,r.father);
            }
        }
        copy(n,c.father,right.father);
        last+=w[d[last+1].val];
    }
    for(int i=1;i<=maxval;++i)
        if(w[i]){
            last=i;
            break;
        }
    for(int i=last;i<=maxval;++i){
        if(ans[i]&&i!=last){
            ans[i]=(ans[i]*ans[last])%mod;
            last=i;
        }
    }
    printf("%d\n",ans[last]);
    return 0;
}

 

posted @ 2018-04-11 11:49  Konoset  阅读(168)  评论(0编辑  收藏  举报