【题解】P8315 [COCI 20212022 #4] Šarenlist

P8315 [COCI 20212022 #4] Šarenlist

题意

给定一棵树,\(n\) 个节点,给定了 \(m\) 条路径的起点和终点,有 \(k\) 种颜色,现在要给每条边染上一种颜色。

求有多少种染色方式,使得对于每条给定的条路径,都满足路径上至少有两种颜色。

答案对 \(10^9+7\) 取模。

\(n\le 60\)\(m\le 15\)

题解

知识点:二项式反演,组合数学,容斥原理。

启发:

  • 正难则反。

到了树上。

\(g_k\) 为恰好有 \(k\) 条路径不满足条件的方案数。

\(f_k\) 为钦定 \(k\) 条路径使得不满足条件,未涉及的边随便染色的方案数。

有如下关系,

\[\large \displaystyle f_k=\sum_{i=k}^m \binom{i}{k}\times g_i \]

二项式反演,

\[\large \displaystyle g_k=\sum_{i=k}^m \binom{i}{k}\times (-1)^{i-k} f_i \]

注意到 \(m\le 15\),这暗示可以 \(O(2^m)\) 枚举每一条路径的是否不满足。

先与处理出每一条路径包含的边的编号,存起来。

设当前状态为 \(s\),将 \(s\) 中为 \(1\) 的位对应的路径取出,在并查集上,依次将每一条路径中的边合并到同一连通块,当两条路径有边交集时,他们都合并到同一连通块。

要让选出来都路径都不合法,则在同一连通块的边得染上同一种颜色,设一共 \(c\) 个连通块,则染色方案为 \(k^c\),累加到 \(f_{\operatorname{popcount(s)}}\)

答案即为 \(g_0\),此时 \(\displaystyle g_0=\sum_{i=0}^m (-1)^{i} f_i\),退化为了一般的容斥原理。

#include<bits/stdc++.h>
using namespace std;

#define rep(i,l,r) for(int i=(l);i<=(r);++i)
#define per(i,l,r) for(int i=(r);i>=(l);--i)
#define pr pair<int,int>
#define fi first
#define se second
#define pb push_back
#define all(x) (x).begin(),(x).end()
#define sz(x) (x).size()
#define bg(x) (x).begin()
#define ed(x) (x).end()

#define N 66
#define int long long

const int mod=1e9+7;
int n,m,k,F[N];
bitset<N>f;
pr a[N],pre[N];
vector<pr>e[N];
vector<int>p[N];

inline int qpow(int a,int b){
    int ans=1;

    while(b){
        if(b&1){
            ans=ans*a%mod;
        }
        a=a*a%mod;
        b>>=1;
    }

    return ans;
}

inline void dfs(int k){
    f[k]=1;

    for(pr x:e[k]){
        if(f[x.fi]){
            continue;
        }

        pre[x.fi]={k,x.se};
        dfs(x.fi);
    }
}

struct dsu{
    int fa[N];

    inline void init(){
        rep(i,1,n){
            fa[i]=i;
        }
    }

    inline int ask(int k){
        if(fa[k]==k){
            return k;
        }
        return ask(fa[k]);
    }

    inline bool mg(int x,int y){
        x=ask(x),y=ask(y);

        if(x==y){
            return 0;
        }

        fa[x]=y;

        return 1;
    }
}d;

inline void sol(int s){
    vector<int>v;

    rep(i,1,m){
        if((s>>(i-1))&1){
            v.pb(i);
        }
    }

    d.init();

    for(int x:v){
        int len=sz(p[x]);

        rep(i,1,len-1){
            d.mg(p[x][i],p[x][i-1]);
        }
    }

    int ans=1;

    rep(i,1,n-1){
        if(d.fa[i]==i){
            ans=ans*k%mod;
        }
    }

    F[sz(v)]=(F[sz(v)]+ans)%mod;
}

inline int neg(int x){
    return x&1?-1:1;
}

signed main(){
    // freopen(".in","r",stdin);
    // freopen(".out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);

    cin>>n>>m>>k;

    rep(i,1,n-1){
        int u,v;
        cin>>u>>v;
        e[u].pb({v,i});
        e[v].pb({u,i});
    }

    rep(i,1,m){
        cin>>a[i].fi>>a[i].se;

        rep(j,1,n){
            f[j]=0;
            pre[j]={0,0};
        }

        dfs(a[i].fi);

        int u=a[i].se;
        while(pre[u].se){
            p[i].pb(pre[u].se);

            u=pre[u].fi;
        }
    }

    rep(s,0,(1<<m)-1){
        sol(s);
    }

    int ans=0;

    rep(i,0,m){
        ans=(ans+
            neg(i)*F[i]
            +mod
        )%mod;
    }

    cout<<ans;

    return 0;
}
posted @ 2025-06-28 17:20  Lucyna_Kushinada  阅读(16)  评论(0)    收藏  举报