【学习笔记】CF1292F Nora‘s Toy Boxes

没啥好说的,接着干吧

这种把性质隐藏得很深的题目非常有趣。

以我现在的水平,估计还是无法独立做出来

首先我们对于 ( i , j ) (i,j) (i,j)如果满足 a i ∣ a j a_i|a_j aiaj,那么 i i i j j j连一条边。

当然,这道题并不是随便给出一个图那么简单。我们可以推出几个简单的性质:

1.1 1.1 1.1 如果 i → j , j → k i\to j,j\to k ijjk,那么 i → k i\to k ik (传递闭包性质)
1.2 1.2 1.2 这是一个 D A G DAG DAG

这个性质看起来非常亲切啊。这样我们每次选择的 i i i一定是入度为 0 0 0的点,并且这个点一定不会被删除。更神奇的是,这样的点不会超过 m 4 \frac{m}{4} 4m个。这个用抽屉原理不难理解。

那么我们考虑倒着做,一个点能够被加入当且仅当存在一个度数 > 0 >0 >0并且有连边的关键点,这里的 d p dp dp非常具有人类智慧,我们需要维护一个标记。假设关键点的集合为 S S S,最开始所有关键点都没有标记,假设加入的点没有重复,如果这个点能到达的关键点当中存在一个点 p p p被标记,那么这个点是被删除的,否则这个点没有被删除;然后把这个点能到达的所有关键点打上标记。

那么我们考虑,如果 S S S发生了变化,说明这个点之前没有被加入过,否则,这个点显然被删除了,注意到 S S S是一个并集,那么我们只需要记录之前被删除的点数目即可。这里有一个小小的推导过程(我在这里还是呈现出来了):记数字 i i i对应的关键点集合为 S i S_i Si,那么没有被删除的点的 S i S_i Si显然是不交的,并且被删除的节点 j j j对应的 S j S_j Sj不可能使这些 S i S_i Si重新相交(这可以画图通过贪心证明,因为要使得删除的点的数目最多)。因此,假设有 s z sz sz个点加入后不会改变 S S S的大小,并且之前已经删除了 i i i个点,那么能加入的点的数目为 s z − i − 1 sz-i-1 szi1(其中一个点没有被删除)。这样就做完了。当然因为我们是对于每一个弱连通块计数所以不难证明这个连通块当中恰好只有一个点没有被删除。

最后把所有连通块的答案合并起来即可。

复杂度 O ( 2 m 4 n 2 ) O(2^\frac{m}{4}n^2) O(24mn2)

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define db double
using namespace std;
const int mod=1e9+7;
int n,m,a[65],vis[65],fa[65],p[65],binom[65][65],g[65],pos[65],sz[1<<15],m2;
ll f[1<<15][65],res=1;
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
void unionset(int x,int y){
    int u=find(x),v=find(y);
    if(u!=v)fa[u]=v;
}
void add(ll &x,ll y){
    x=(x+y)%mod;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n;
    for(int i=0;i<=60;i++)binom[i][0]=1;
    for(int i=1;i<=60;i++){
        for(int j=1;j<=i;j++){
            binom[i][j]=(binom[i-1][j]+binom[i-1][j-1])%mod;
        }
    }
    for(int i=1;i<=n;i++){
        int x;cin>>x,a[x]=1;
    }
    for(int i=1;i<=60;i++)fa[i]=i;
    for(int i=1;i<=60;i++){
        for(int j=i+1;j<=60;j++){
            if(a[i]&&a[j]&&j%i==0){
                vis[j]=1;
                unionset(i,j);
            }
        }
    }
    int cnt=0;
    for(int i=1;i<=60;i++){
        if(a[i]&&fa[i]==i){
            m=m2=0;
            for(int j=1;j<=60;j++){
                if(a[j]&&find(j)==i){
                    if(vis[j]){
                        pos[m2++]=j;
                    }
                    else if(j<=30){
                        p[m++]=j;
                    }
                }
            }
            assert(m<=15);
            if(!m2)continue;
            memset(f,0,sizeof f);
            for(int j=0;j<m2;j++){
                g[j]=0;
                for(int k=0;k<m;k++){
                    if(pos[j]%p[k]==0){
                        g[j]|=1<<k;
                    }
                }
                assert(g[j]);
                f[g[j]][0]++;
            }
            for(int j=0;j<1<<m;j++){
                sz[j]=0;
                for(int k=0;k<m2;k++){
                    if((g[k]&j)==g[k]){
                        sz[j]++;
                    }
                }
            }
            for(int k=0;k<=m2;k++){
                for(int j=0;j<1<<m;j++){
                    if(f[j][k]){
                        //fixed
                        assert(k<sz[j]);
                        add(f[j][k+1],f[j][k]*(sz[j]-k-1));
                        for(int l=0;l<m2;l++){
                            //fixed
                            if((j|g[l])!=j&&(j&g[l])!=0){
                                add(f[j|g[l]][k+1],f[j][k]);
                            }
                        }
                    }
                }
            }
            ll tot=0;
            for(int j=0;j<1<<m;j++){
                add(tot,f[j][m2-1]);
            }
            res=res*tot%mod*binom[cnt+m2-1][m2-1]%mod;
            cnt+=m2-1;
        }
    }
    cout<<res;
}
posted @ 2023-04-06 22:45  仰望星空的蚂蚁  阅读(14)  评论(0)    收藏  举报  来源