[WC2018] 州区划分

[UOJ 348][LOJ 2340][WC2018] 州区划分

题意

LOJ题面

小 S 现在拥有 \(n\) 座城市,第 \(i\) 座城市的人口为 \(w_i\),城市与城市之间可能有双向道路相连。

现在小 S 要将这 \(n\) 座城市划分成若干个州,每个州由至少一个城市组成,每个城市在恰好一个州内。

假设小 S 将这些城市划分成了 \(k\) 个州,设 \(V_i\) 是第 \(i\) 个州包含的所有城市所组成的集合。定义一条道路是一个州的内部道路,当且仅当这条道路的两个端点城市都在这个州内。如果一个州内部存在一条起点终点相同,不经过任何不属于这个州的城市,且经过这个州的所有内部道路都恰好一次并且经过这个州的所有城市至少一次的路径(路径长度可以为 \(0\)),则称这个州是不合法的。

定义第 \(i\) 个州的满意度为:第 \(i\) 个州的人口在前 \(i\) 个州的人口中所占比例的 \(p\) 次幂,即:
\[\left( \frac {\sum_{x \in V_i} w_x} {\sum_{j=1}^i \sum_{x \in V_j} w_x} \right)^p\]

定义一个划分的满意度为所有州的满意度的乘积

求所有合法的划分方案的满意度之和。

答案对 \(998244353\) 取模。

两个划分 \(\{V_1 \dots V_k\}\)\(\{C_1 \dots C_s\}\) 是不同的,当且仅当 \(k \neq s\),或存在某个 \(1 \leq i \leq k\),使得 \(V_i \neq C_i\)

其中 \(n\le 21\), 单点时限 \(10\texttt{s}\)

题解

首先我们发现这个 \(n\) 不大于是我们考虑状压. 定义 \(dp(S)\) 表示集合 \(S\) 中所有合法划分方案的满意度乘积.

然后发现这个划分是有序的, 不难想到枚举子集作为最后一个划分集合来DP.

\(sum(S)=\sum\limits_{v\in S}w_v\), \(F(S)=[valid (S)]sum(S)^p\) , 则不难得到一个 \(O(n3^n)\) 的暴力DP:
\[ dp(S)=\frac 1 {sum(S)^p} \sum_{T\subset S} dp(T)F(S-T) \]
容易发现是一个裸的子集卷积, 随手FWT算一下就可以了. 虽然式子是自己卷自己, 但是子集卷积自带分层buff于是直接做就可以了.

复杂度是 \(O(n^22^n)\).

判断合法的部分可以用欧拉回路存在的充要条件, 也就是图联通且每个点度数都是偶数.

不过卷积循环顺序搞得我有点怀疑人生. (垃圾luogu评测机吃枣药丸)

参考代码

#include <bits/stdc++.h>

const int MAXN=23;
const int MAXL=3e6+10;
const int MOD=998244353;

int v;
int e;
int p;
int w[MAXN];
int ufs[MAXN];
int sum[MAXL];
int deg[MAXN];
int dp[MAXN][MAXL];
int vx[MAXN][MAXL];
std::pair<int,int> E[MAXN*MAXN];

bool Check(int);
int FindRoot(int);
void FWT(int*,int);
void IFWT(int*,int);
int Pow(int,int,int);
int Sub(int,int,int);
int Add(int,int,int);

int main(){
    scanf("%d%d%d",&v,&e,&p);
    for(int i=0;i<e;i++){
        scanf("%d%d",&E[i].first,&E[i].second);
        --E[i].first;
        --E[i].second;
    }
    for(int i=0;i<v;i++)
        scanf("%d",w+i);
    int maxs=1<<v;
    for(int s=0;s<maxs;s++){
        int cnt=0;
        for(int i=0;i<v;i++)
            if((1<<i)&s)
                ++cnt,sum[s]+=w[i];
        sum[s]=Pow(sum[s],p,MOD);
        vx[cnt][s]=Check(s)?sum[s]:0;
    }
    dp[0][0]=1;
    FWT(dp[0],maxs);
    for(int i=1;i<=v;i++){
        FWT(vx[i],maxs);
        for(int j=0;j<i;j++)
            for(int s=0;s<maxs;s++)
                (dp[i][s]+=1ll*dp[j][s]*vx[i-j][s]%MOD)%=MOD;
        IFWT(dp[i],maxs);
        for(int s=0;s<maxs;s++)
            if(__builtin_popcount(s)!=i)
                dp[i][s]=0;
            else
                dp[i][s]=1ll*dp[i][s]*Pow(sum[s],MOD-2,MOD)%MOD;
        if(i!=v)
            FWT(dp[i],maxs);
    }
    printf("%d\n",dp[v][maxs-1]);
    return 0;
}

bool Check(int s){
    for(int i=0;i<v;i++){
        ufs[i]=i;
        deg[i]=0;
    }
    int cnt=__builtin_popcount(s);
    for(int i=0;i<e;i++){
        if(((1<<E[i].first)&s)&&((1<<E[i].second)&s)){
            ++deg[E[i].first];
            ++deg[E[i].second];
            if(FindRoot(E[i].first)!=FindRoot(E[i].second)){
                ufs[FindRoot(E[i].first)]=FindRoot(E[i].second);
                --cnt;
            }
        }
    }
    if(cnt!=1)
        return true;
    for(int i=0;i<v;i++)
        if(((1<<i)&s)&&(deg[i]&1))
            return true;
    return false;
}

void FWT(int* a,int len){
    for(int i=1;i<len;i<<=1)
        for(int j=0;j<len;j+=i<<1)
            for(int k=0;k<i;k++)
                a[j+k+i]=Add(a[j+k+i],a[j+k],MOD);
}

void IFWT(int* a,int len){
    for(int i=1;i<len;i<<=1)
        for(int j=0;j<len;j+=i<<1)
            for(int k=0;k<i;k++)
                a[j+k+i]=Sub(a[j+k+i],a[j+k],MOD);
}

int Pow(int a,int n,int p){
    int ans=1;
    while(n>0){
        if(n&1)
            ans=1ll*a*ans%p;
        a=1ll*a*a%p;
        n>>=1;
    }
    return ans;
}

int FindRoot(int x){
    return ufs[x]==x?ufs[x]:ufs[x]=FindRoot(ufs[x]);
}

inline int Sub(int a,int b,int p){
    return a<b?a-b+p:a-b;
}

inline int Add(int a,int b,int p){
    return a+b>=p?a+b-p:a+b;
}

猜你喜欢

转载自www.cnblogs.com/rvalue/p/10419819.html