bzoj 2734 集合选数

来源:互联网 发布:win10如何关闭端口 编辑:程序博客网 时间:2024/06/06 13:56
构造矩阵

1 3  9  27…

2 6 18 54…

4 12 36 108…

……

每个数是上面的数乘2,左面的数乘3。这样进行状压dp就是相邻的格子不能选的方案数。

如何判断一个二进制数没有两个连续的1? x&(x<<1)=0

如果有的数没有出现过,就以它为左上角元素再构造一个矩阵。

这是怎么想到的?!!

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>

#define ll long long
#define inf 1e9
#define eps 1e-10
#define md 1000000001
#define N
using namespace std;
int a[20][15],b[20],mi[20];
ll f[20][5010];
bool vis[100010];
int n;
ll ans=1;
void add(ll &x,ll y)
{
x=x+y; if (x>=md) x-=md;
}
void solve(int x)
{
for (int i=1,now=x;i<=18;i++,now*=2)
{
if (now<=n)
{
a[i][1]=now;
vis[now]=1;
b[i]=mi[1];
}
else
{
a[i][1]=n+1;
b[i]=0;
now=n+1;
}
}
for (int i=1;i<=18;i++)
for (int j=2;j<=11;j++)
{
int x=a[i][j-1]*3;
if (x<=n)
{
a[i][j]=x;
vis[x]=1;
b[i]+=mi[j];
}
else
{
a[i][j]=n+1;
}
}
for (int i=1;i<=18;i++)
for (int j=0;j<=b[i];j++)
f[i][j]=0;
b[0]=1; f[0][0]=1;
for (int i=0;i<18;i++)
{
for (int j=0;j<=b[i];j++)
if (f[i][j])
{
for (int k=0;k<=b[i+1];k++)
if ( (!(k&(k<<1))) && (!(j&k))) add(f[i+1][k],f[i][j]);
}
}
ans=ans*f[18][0]%md;
}


int main()
{
mi[1]=1; for (int i=2;i<=18;i++) mi[i]=mi[i-1]<<1;
ans=1;
scanf("%d",&n);
for (int i=1;i<=n;i++)
if (!vis[i]) solve(i);
printf("%lld\n",ans);
return 0;
}



0 0
原创粉丝点击