[HNOI2012]集合选数
嘟嘟嘟
这题真是一道好题。
看到题中的限制,我就一直想dp,可是设出来的状态都无法转移,最后还是看题解了。
题解真是妙,太妙了。
就是我们构造一个矩阵,满足\(a[i][j] = a[i][j - 1] * 2(j > 1), a[i][1] = a[i - 1][1] * 3\)。这个矩阵最大不过\(12 * 18\),于是可以状压dp,每一次选择同一行中不选相邻的,两行之间不在同一列的转移。这个转移就很显然了。
但是如果以\(a[1][1] = 1\)构造矩阵,有些数不会构造到,因此我们枚举所有的数,如果一个数不在矩阵中,就以他为左上角再构造个矩阵,这样答案就是每一次构造后dp的乘积了。
这题稍微得卡个常,比如把不相邻的1的二进制数都预处理出来,然后把加法取模换成减模数(换完后贼块),就行了。
#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<cctype>
#include<vector>
#include<stack>
#include<queue>
using namespace std;
#define enter puts("")
#define space putchar(' ')
#define Mem(a, x) memset(a, x, sizeof(a))
#define In inline
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-8;
const int maxn = 1e5 + 5;
const ll mod = 1e9 + 1;
inline ll read()
{
ll ans = 0;
char ch = getchar(), last = ' ';
while(!isdigit(ch)) last = ch, ch = getchar();
while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
if(last == '-') ans = -ans;
return ans;
}
inline void write(ll x)
{
if(x < 0) x = -x, putchar('-');
if(x >= 10) write(x / 10);
putchar(x % 10 + '0');
}
int n;
bool vis[maxn];
int s[19][1 << 19], len[19];
In void init()
{
for(int i = 1; i < 19; ++i)
for(int j = 0; j < (1 << i); ++j) if(!(j & (j << 1))) s[i][++len[i]] = j;
}
int f[13][19], lim[13], Lim;
ll dp[13][1 << 19];
In ll inc(ll a, ll b) {return a + b >= mod ? a + b - mod : a + b;}
In ll Dp(int x)
{
for(int i = 1; i <= 12; ++i)
{
if(i == 1) f[i][1] = x;
else f[i][1] = f[i - 1][1] * 3;
if(f[i][1] > n) {Lim = i - 1; break;}
vis[f[i][1]] = 1;
for(int j = 2; j <= 18; ++j)
{
if((f[i][j - 1] << 1) > n) {lim[i] = j - 1; break;}
vis[f[i][j] = (f[i][j - 1] << 1)] = 1;
}
}
for(int i = 0; i < (1 << lim[1]); ++i) dp[1][i] = 0;
for(int i = 1; i <= len[lim[1]]; ++i) dp[1][s[lim[1]][i]] = 1;
for(int i = 2; i <= Lim; ++i)
for(int j = 1; j <= len[lim[i]]; ++j)
{
dp[i][s[lim[i]][j]] = 0;
for(int k = 1; k <= len[lim[i - 1]]; ++k)
if(!(s[lim[i]][j] & s[lim[i - 1]][k]))
dp[i][s[lim[i]][j]] = inc(dp[i][s[lim[i]][j]], dp[i - 1][s[lim[i - 1]][k]]);
}
ll ret = 0;
for(int i = 1; i <= len[lim[Lim]]; ++i) ret = inc(ret, dp[Lim][s[lim[Lim]][i]]);
return ret;
}
int main()
{
init();
n = read();
ll ans = 1;
for(int i = 1; i <= n; ++i) if(!vis[i]) ans = ans * Dp(i) % mod;
write(ans), enter;
return 0;
}