数学口胡记录

[ABC213G] Connectivity 2

\(tag\):计数,\(dp\),容斥

\(n\)比较小,考虑状压。

\(dp_s\)表示只有\(s\)中的点都联通的方案数。\(g_s\)表示\(s\)的所有子图个数,若\(s\)中有\(cnt\)条边,那么\(g_s=2^{cnt}\)

考虑计算\(dp_s\),可以用总方案减去不合法方案,对于不合法方案数枚举\(s\)的子集\(t\),钦定\(t\)中节点联通,其余\(s-t\)中的点随意。

那么有\(dp_s=g_s-\sum\limits_{t \in s}{dp_t*g_{s \oplus t}}\)

但是发现这样实际上会算重,可能一个点集在枚举中是联通的,在任意连时也是联通的。

因此考虑在枚举\(s\)时强制将某个点选入,这样就一定不会重复。取任意点答案都是一样的,如取\(1\)即可。

那么有\(dp_s=g_s-\sum\limits_{1\in t \in s}{dp_t*g_{s \oplus t}}\)

\(up\)为全集,答案就是\(ans_i=\sum\limits_{1\in s ,i \in s}dp_s*g_{up \oplus s}\)

点击查看代码
#include<bits/stdc++.h>

using namespace std;

const int MAXN=25;
const int MAXM=405;
const int MAXS=5e5+5;

int n,m;
vector <int> G[MAXN];

#define ll long long

const ll MOD=998244353;
ll g[MAXS],dp[MAXS];
ll mul2[MAXM],ans[MAXN];

int main(){
    mul2[0]=1;
    for (int i=1;i<MAXM;i++) mul2[i]=mul2[i-1]*2ll%MOD;
    cin>>n>>m;
    for (int i=1;i<=m;i++){
        int u,v;
        scanf("%d %d",&u,&v);
        G[u].push_back(v);G[v].push_back(u);
    }
    int up=(1<<n)-1;
    for (int mask=0;mask<=up;mask++){
        int cnt=0;
        for (int i=1;i<=n;i++){
            if (!(mask&(1<<(i-1)))) continue;
            for (const auto &j:G[i]){
                if (mask&(1<<(j-1))) cnt++;
            }
        }
        cnt/=2;
        g[mask]=mul2[cnt];
    }
    dp[0]=1;
    for (int mask=1;mask<=up;mask++){
        dp[mask]=g[mask];
        for (int mask2=mask;mask2;mask2=(mask2-1)&mask){
            if (mask2==mask) continue;
            if (mask2&1){
                dp[mask]-=dp[mask2]*g[mask^mask2]%MOD;
                dp[mask]%=MOD;dp[mask]+=MOD;dp[mask]%=MOD;         
            }
        }
    }
    for (int mask=0;mask<=up;mask++){
        for (int i=2;i<=n;i++){
            if ((mask&1)&&(mask&(1<<(i-1)))) ans[i]+=dp[mask]*g[up^mask]%MOD,ans[i]%=MOD;
        }
    }
    for (int i=2;i<=n;i++) printf("%lld\n",ans[i]);
    return 0;
}

树上竞技

题意:给定一个\(n\)个点的树,每次选择\(m\)个点,设\(s\)为到这\(m\)个点总距离和最小的点,这个大小为\(m\)的集合的价值即为\(m\)个点到\(s\)距离和。求所有大小为\(m\)的集合的价值之和,对\(998244353\)取模。\(1 \leq m \leq n \leq 10^6\)

\(tag\):排列组合,推式子

发现价值不好从点入手,因此考虑每条边的贡献。

称在集合中的点为关键点,对于每条边,枚举其两个端点子树中包含的关键点个数\(i\),显然让关键点更少的一边向关键点更多的一边走更优,因此这条边贡献为\(\min ( i,m-i )\)

所以对一条边,设其两个端点子树大小分别为\(s\),\(n-s\)那么贡献为:

\(\sum\limits_{i=1}^{m-1} \binom{s}{i} \binom{n-s}{m-i} \times \min ( i,m-i )\)

发现\(min\)难以处理,考虑将其拆开。发现这个结构是对称的,令\(k=\frac{m-1}{2}\)因此有:

\(=2\times \sum\limits_{i=1}^{k} \binom{s}{i} \binom{n-s}{m-i} \times i+ \left[m\pmod 2 \equiv 0 \right] \binom{s}{\frac{m}{2}} \binom{n-s}{\frac{m}{2}}\)

后面那部分可以直接算,考虑快速计算前面一部分。

有吸收公式有:

\(=2s\times \sum\limits_{i=1}^{k} \binom{s-1}{i-1} \binom{n-s}{m-i}\)

\(g(s)=\sum\limits_{i=1}^k \binom{s-1}{i-1} \binom{n-s}{m-1}\),发现具有组合意义,可以看做从\(n-s\)个数中选出\(m-1\)个数,并且要求前\(s-1\)个数中选的不超过\(k-1\)

那么\(g(s)\)可以递推,具体来说,考虑从\(g(s)\)\(g(s+1)\)中从合法变为不合法的个数,显然只有前\(s-2\)个中恰好选\(k-1\)个,第\(s-1\)个钦定选择,剩下\(n-s\)中再选\(m-k-1\)个不符合。

所以有\(g(1)=\binom{n-1}{m-1},g(i)=g(i-1)-\binom{i-2}{k-1}\times\binom{n-i}{m-k-1}\)

然后就做完了,时间复杂度\(O(n)\)

点击查看代码
#include<bits/stdc++.h>

using namespace std;

template <class T>
void read(T &x){
    x=0;char c=getchar();bool f=0;
    while(!isdigit(c)) f=c=='-',c=getchar();
    while(isdigit(c)) x=x*10+c-'0',c=getchar();
    x=f? (-x):x;
}

const int MAXN=1e6+5;

#define ll long long

const ll MOD=1e9+7;

int n,m;

vector <int> G[MAXN];

ll fac[MAXN],inv[MAXN];

ll g[MAXN];

int siz[MAXN];

ll power(ll a,ll b,ll MOD){
    ll ret=1;
    for (;b;b>>=1){
        if (b&1) ret=(ret*a)%MOD;
        a=(a*a)%MOD;
    }
    return ret;
}

void init(int n){
    fac[0]=1;
    for (int i=1;i<=n;i++) fac[i]=fac[i-1]*(ll)i%MOD;
    inv[n-1]=power(fac[n-1],MOD-2,MOD);
    for (int i=n-1;i>=1;i--){
        inv[i-1]=inv[i]*(ll)i%MOD;
    }
}

ll C(int n,int m){
    if (m>n||m<0) return 0;
    return fac[n]*inv[n-m]%MOD*inv[m]%MOD;
}

ll ans;

void dfs(int u,int fa){
    siz[u]=1;
    for (const auto &v:G[u]){
        if (v==fa) continue;
        dfs(v,u);
        siz[u]+=siz[v];
        ans+=(g[siz[v]]+g[n-siz[v]])%MOD;
        ans%=MOD;
        if (m%2==0) ans+=C(siz[v],m/2)*C(n-siz[v],m/2)%MOD*(m/2)%MOD,ans%=MOD;
    }
}

int main(){
    init(MAXN);
    read(n);read(m);
    for (int i=2;i<=n;i++){
        int v;
        read(v);
        G[i].push_back(v);G[v].push_back(i);
    }
    int k=(m-1)/2;
    if (k>=1) g[1]=C(n-1,m-1);
    else g[1]=0;
    for (int i=2;i<=n;i++){
        g[i]=(g[i-1]-C(i-2,k-1)*C(n-i,m-k-1)%MOD);
        g[i]%=MOD;g[i]+=MOD;g[i]%=MOD;
    }
    for (int i=1;i<=n;i++) g[i]*=(ll)i,g[i]%=MOD;
    dfs(1,0);
    printf("%lld\n",ans);
    return 0;
}
posted @ 2023-08-17 00:12  Katyusha_Lzh  阅读(30)  评论(2)    收藏  举报