LGP3226 [HNTS 2012] 集合选数 学习笔记

LGP3226 [HNTS 2012] 集合选数 学习笔记

Luogu Link

题意简述

给定一个整数 \(n\)。问对于正整数集合 \(\{1,2,\dots,n\}\),其有多少个子集 \(s\) 满足:\(\forall x\in s\)\(2x,3x\notin s\)

\(1\le n\le 10^5\)

做法解析

妙题。

对于每一个与 \(2,3\) 互质的整数 \(u\),我们考虑一个矩形 \(A_u\),满足 \(a_{i,j}=2^i\times 3^j\)。原限制转化为不能选此矩形中相邻的两个数。原问题转化为求所有 \(A_u\) 的选法数量的乘积。

我们怎么求每一个 \(A_u\) 的方案数呢?状压就没了。时间复杂度能过。

代码实现

#include <bits/stdc++.h>
using namespace std;
using namespace obasic;
using namespace omodint;
using mint=modint<int(1e9+1)>;
const int MaxN=1e5+5,MaxS=(1<<20);
int N,vld[MaxS],alf,lim[12];mint dp[12][MaxS];
mint solve(int x){
    static int n,m[12],a[12][20];
    for(int i=1;i<=alog(N,3)+1;i++){
        a[i][1]=(i==1?x:a[i-1][1]*3);
        if(a[i][1]>N)break;n=i,m[i]=1;
        for(int j=2;j<=log2(N/i)+1;j++){
            a[i][j]=a[i][j-1]*2;
            if(a[i][j]>N)break;m[i]=j;
        }
        lim[i]=(1<<m[i])-1;
    }
    mint res=0;
    for(int i=0;i<=lim[1];i++)dp[1][i]=vld[i];
    for(int i=2;i<=n;i++){
        for(int j=0;j<=lim[i];j++){
            if(!vld[j])continue;dp[i][j]=0;
            int u=lim[i-1]-j;
            for(int k=u;;k=u&(k-1)){
                if(vld[k])dp[i][j]+=dp[i-1][k];
                if(k==0)break;
            }
        }
    }
    for(int i=0;i<=lim[n];i++)res+=dp[n][i];
    return res;
}
mint fans;
int main(){
    readi(N),fans=1,alf=(1<<(int(log2(N))+1))-1;
    for(int i=0;i<=alf;i++)vld[i]=(((i<<1)&i)?0:1);
    for(int i=1;i<=N;i++)if(i%2&&i%3)fans*=solve(i);
    writi(miti(fans));
    return 0;
}
posted @ 2025-06-18 15:35  矞龙OrinLoong  阅读(9)  评论(0)    收藏  举报