[BZOJ 2734] 集合选数

Link:

BZOJ 2734 传送门

Solution:

真是奥妙重重的建模啊.....

我们发现$x,2*x,3*x$这些数太分散了,难以处理

于是我们构建这样的表格:

x  3x  9x  27x....

2x  6x  18x  54x...

4x  12x   36x  108x...

...   .....    .....    ......

将问题转化为求表格上任意两点不相邻的点集数

由于$2^{17}>100000$,因此直接对每一行的信息状压就行了

设$dp[i][j]$为到第$i$行且其状态为$j$时的方案总数,上一层状态为$k$,判断!(k&j) && !(j&(j>>1))即可

Note:由于一个表格不能包含所有数,因此要建多个表格,结果相乘

Code:

//by NewErA
#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int MOD=1e9+1;

const int MAXN=100005;
ll n,dp[50][MAXN],dat[50][50],len[50],last;
bool vis[MAXN];

ll cal(int x)
{
    dat[1][1]=x;
    for(int i=1;;i++)
    {
        if(i>1) dat[i][1]=dat[i-1][1]*2;
        if(dat[i][1]>n){last=i-1;break;}
        vis[dat[i][1]]=true;
        
        for(int j=2;;j++)
        {
            dat[i][j]=dat[i][j-1]*3;
            if(dat[i][j]>n){len[i]=j-1;break;}
            vis[dat[i][j]]=true;
        }
    }
    
    for(int i=0;i<=last+1;i++)
        for(int j=0;j<=(1<<len[i]);j++) dp[i][j]=0;
    
    dp[0][0]=1;len[0]=1; //状压DP
    for(int i=0;i<=last;i++)
        for(int j=0;j<(1<<len[i]);j++)
            if(dp[i][j])
                for(int k=0;k<(1<<len[i+1]);k++)
                    if(!(j&k) && !(k&(k>>1))) dp[i+1][k]=(dp[i+1][k]+dp[i][j])%MOD;
    return dp[last+1][0];
}

int main()
{
    cin >> n;
    ll res=1;
    for(int i=1;i<=n;i++)
        if(!vis[i]) res=res*cal(i)%MOD;
    cout << res;
    return 0;
}

Review:

1、当信息较为分散时,通过构建矩阵等各类方式将相关条件集中处理

2、求矩阵中任意两点不相邻的点集数(经典问题)

状压DP的应用,整体$!(j&k)$和$!k&(k>>1)$判断的方式还行

猜你喜欢

转载自www.cnblogs.com/newera/p/9141390.html