[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;
}
posted @ 2019-02-25 11:22  mrclr  阅读(227)  评论(0编辑  收藏  举报