• Luogu P3226 集合选数 题解报告


    题目传送门

    【题目大意】

    给定一个数$n$,从集合${1,2,…,n}$中选数,求满足“如果$x$选了,就不能选$2x$和$3x$”的子集个数(包括空集)

    【思路分析】

    我觉得这题挺妙的,要转化为在矩阵中选数,类似这样的矩阵$downarrow$

    $$egin{matrix}x&2x&4x&cdots\3x&6x&12x&cdots\9x&18x&36x&cdots\vdots&vdots&vdotsend{matrix}$$

    然后你发现要满足题目要求就是在矩阵内不能选相邻的数(不能上下相邻也不能左右相邻),于是我们就可以愉快地用状压DP求答案啦!

    要注意的就是为了保证不漏答案,我们要把$[1,n]$内每一个既不是2的倍数也不是3的倍数的数作为$x$构造矩阵,然后跑一遍状压DP求答案,这样才能涵盖所有的数,最后根据乘法原理就可以求出整道题目的答案了。

    详见代码(有注解)

    【代码实现】

     1 #include<cstdio>
     2 #include<iostream>
     3 #include<cstring>
     4 #include<algorithm>
     5 #include<cmath>
     6 #include<queue>
     7 #define g() getchar()
     8 #define rg register
     9 #define ll long long
    10 #define go(i,a,b) for(rg ll i=a;i<=b;i++)
    11 #define back(i,a,b) for(rg ll i=a;i>=b;i--)
    12 #define db double
    13 #define il inline
    14 #define pf printf
    15 #define mem(a,b) memset(a,b,sizeof(a))
    16 using namespace std;
    17 ll fr(){
    18     ll w=0,q=1;
    19     char ch=g();
    20     while(ch<'0'||ch>'9'){
    21         if(ch=='-') q=-1;
    22         ch=g();
    23     }
    24     while(ch>='0'&&ch<='9') w=(w<<1)+(w<<3)+ch-'0',ch=g();
    25     return w*q;
    26 }
    27 const ll mod=1000000001;
    28 const int maxn=(1<<18)+1;
    29 const int N=100002;
    30 ll n,a[20][20],end,lie[20],mx[20],f[20][maxn];
    31 //f[i][j]表示枚举到当前矩阵第i列状态为j的方案数
    32 bool ok[maxn];
    33 ll ans=1,as;
    34 il void build(rg ll x){
    35     go(i,1,11){
    36         if(i==1) a[i][1]=x;
    37         else a[i][1]=a[i-1][1]*3;
    38         if(a[i][1]>n) break;
    39         end=i,lie[i]=1;//lie[i]记录第i行有多少列
    40         go(j,2,18){
    41             a[i][j]=a[i][j-1]<<1;
    42             if(a[i][j]>n) break;
    43             lie[i]=j;
    44         }
    45         mx[i]=(1<<lie[i])-1;//mx[i]记录第i行所有状态压缩后的最大值
    46     }
    47     return;
    48 }
    49 il void work(rg ll x){
    50     as=0;
    51     go(i,0,mx[1]) f[1][i]=ok[i];
    52     go(i,2,end) go(j,0,mx[i]){
    53         if(!ok[j]) continue;//如果这个状态不合法就跳过
    54         f[i][j]=0;//这里要手动赋初始值,用memset会T
    55         go(k,0,mx[i-1])
    56             if(ok[k]&&((k&j)==0)) f[i][j]=(f[i][j]+f[i-1][k])%mod;
    57 //如果上一行的状态合法且与这一行没有选同一列的数,那么就是合法的,加入答案
    58     }
    59     go(i,0,mx[end]) if(ok[i]) as=(as+f[end][i])%mod;
    60 }
    61 int main(){
    62     //freopen("","r",stdin);
    63     //freopen("","w",stdout);
    64     n=fr();
    65     go(i,0,maxn-1) ok[i]=((i<<1)&i)?0:1;
    66     //预处理出单个一行每种状态是否满足条件
    67     go(i,1,n)
    68         if(i%2&&i%3) build(i),work(i),ans=ans*as%mod;
    69     //如果这个数既不是2的倍数也不是3的倍数,那就要造矩阵求答案
    70     pf("%lld
    ",ans);
    71     return 0;
    72 }
    代码戳这里
  • 相关阅读:
    ubuntu中文版man
    每日英语:Auto Makers Accelerate Efforts to Develop Self-Driving Cars
    每日英语:How Often Do Gamblers Really Win?
    每日英语:Nanjing's New Sifang Art Museum Illustrates China's Cultural Boom
    每日英语:Political Gridlock, Beijing Style
    每日英语:How Your Knees Can Predict the Weather
    每日英语:Chinese Show Global Real-Estate Appetite
    每日英语:The Delicate Protocol Of Hugging
    每日英语:Making the Most of Your Lunch Hour
    每日英语:The Most Destructive, Unpredictable Force in Tech
  • 原文地址:https://www.cnblogs.com/THWZF/p/11589035.html
Copyright © 2020-2023  润新知