[HNOI2012]集合选数
[HNOI2012]集合选数
很好的题啊!
首先看到数据范围无从下手,数论?容斥?反正死也想不到状压...
这里有一个很巧妙的转换.
\[\begin{matrix}
1 & 2 & 4 & 8 & ...\\
3 & 6 & 12 & 24 & ...\\
9 & 18 & 36 & 72 & ...\\
27 & 54 & 108 & 216 & ...
\end{matrix}
\]
你会发现这个矩阵你不能同时选相邻的两个数,而这个矩阵的大小最大只有16,那么显然可以状压了.当然这个矩阵不会包含所有的数,于是枚举所有的这样的矩阵做dp.
状压一般都可以减掉很多枝的,跑得过去,复杂度玄学.
#include<bits/stdc++.h>
#define ll long long
#define mod 1000000001
using namespace std;
int dp[2][1<<12],lim[20],n,num[20],cur,pre;
ll mat[20][20],ans=1;
void init(int x)
{
mat[1][1]=x;memset(num,0,sizeof(num));memset(lim,0,sizeof(lim));
for(int i=2;i<=17;i++)mat[i][1]=mat[i-1][1]*2;
for(int i=2;i<=12;i++)
for(int j=1;j<=17;j++)
mat[j][i]=mat[j][i-1]*3;
for(int i=1;i<=17;i++)
for(int j=1;j<=12;j++)
{
if(mat[i][j]<=n)lim[i]^=1<<(j-1),num[i]=j;
else break;
}
}
int DP(int x)
{
int res=0;cur=1;pre=0;
memset(dp,0,sizeof(dp));
dp[cur][0]=1;init(x);//cout<<x<<endl;
for(int i=0;i<17;i++)
{
//cout<<lim[i]<<" ";
for(int j=0;j<(1<<num[i]);j++)
{
if(!dp[cur][j])continue;
int t=dp[cur][j];dp[cur][j]=0;
if(j&(j<<1))continue;
if((j&lim[i])!=j)continue;
//cout<<i<<" "<<j<<" "<<t<<endl;
for(int k=0;k<(1<<num[i+1]);k++)
{
if(k&j)continue;
dp[pre][k]+=t;dp[pre][k]%=mod;
}
}
swap(pre,cur);
}
for(int i=0;i<(1<<12);i++)res+=dp[cur][i],res%=mod;
//cout<<res<<endl;
return res;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
if((i%2)&&(i%3))ans*=DP(i),ans%=mod;
cout<<ans<<endl;
return 0;
}