1. 程式人生 > >bzoj 4197 [Noi2015]壽司晚宴 狀壓dp

bzoj 4197 [Noi2015]壽司晚宴 狀壓dp

題面

題目傳送門

解法

思路是真的神仙

  • 考慮當nn比較小的時候怎麼做,因為質因子的個數不多,所以將是否取質因子的狀態壓成一個二進位制數然後轉移即可
  • 然後發現n500n≤500,質因子個數還是比較多的
  • 那麼我們對於每一個數都分開考慮,可以發現最多隻會有一個質因子>n>\sqrt n
  • 計算一下n\sqrt n2222左右,在這個範圍內的質數很少,只有8個
  • 那麼我們可以將這些質因子的取值狀態壓成一個二進位制數,設f[i][j]f[i][j]表示第一個人選擇了質因子集合ii,第二個人選擇了質因子集合jj的方案數
  • 然後做法可以說就比較顯然了:對於每一個數先求出大於n
    \sqrt n
    的質因子,然後按照這個排序,將大於n\sqrt n的質因子相同的放入一組。對於同一組裡的數,我們可以進行這樣一個dp:設f1[i][j]f1[i][j]表示這個因子給第一個人,f2[i][j]f2[i][j]表示這個因子給第二個人的方案數。轉移十分顯然,這裡就不必再贅述了
  • 考慮將同一組裡的全部求解完畢之後怎麼把答案合併到f[i][j]f[i][j]上,因為在計算的時候會重複計算兩個人都不選擇這個質因子的情況,所以f[i][j]=f1[i][j]+f2[i][j]f[i][j]f[i][j]=f1[i][j]+f2[i][j]-f[i][j]
  • 因為這裡開的是類似於滾動陣列的東西,所以在求解f1,f2f1,f2的時候下標應該倒著列舉,類似於0/1揹包
  • 時間複雜度:O(n216)O(n2^{16})

程式碼

#include <bits/stdc++.h>
#define N 550
using namespace std;
template <typename node> void chkmax(node &x, node y) {x = max(x, y);}
template <typename node>
void chkmin(node &x, node y) {x = min(x, y);} template <typename node> void read(node &x) { x = 0; int f = 1; char c = getchar(); while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();} while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f; } int prime[9] = {0, 2, 3, 5, 7, 11, 13, 17, 19}; int f1[N][N], f2[N][N], ans[N][N]; struct Info { int s, key; bool operator < (const Info &a) const {return key < a.key;} void divide(int x) { for (int i = 1; i <= 8; i++) { if (x % prime[i] != 0) continue; s |= 1 << i - 1; while (x % prime[i] == 0) x /= prime[i]; } key = x; } } a[N]; int main() { int n, Mod; read(n), read(Mod); for (int i = 2; i <= n; i++) a[i].divide(i); sort(a + 2, a + n + 1); ans[0][0] = 1; for (int i = 2; i <= n; i++) { if (a[i].key == 1 || a[i].key != a[i - 1].key || i == 2) memcpy(f1, ans, sizeof(f1)), memcpy(f2, ans, sizeof(f2)); for (int j = 255; ~j; j--) for (int k = 255; ~k; k--) { if ((j & k) > 0) continue; if ((k & a[i].s) == 0) f1[j | a[i].s][k] = (f1[j | a[i].s][k] + f1[j][k]) % Mod; if ((j & a[i].s) == 0) f2[j][k | a[i].s] = (f2[j][k | a[i].s] + f2[j][k]) % Mod; } if (i == n || a[i].key == 1 || a[i].key != a[i + 1].key) for (int j = 0; j <= 255; j++) for (int k = 0; k <= 255; k++) { if ((j & k) > 0) continue; ans[j][k] = ((f1[j][k] + f2[j][k]) % Mod - ans[j][k] + Mod) % Mod; } } int ret = 0; for (int i = 0; i <= 255; i++) for (int j = 0; j <= 255; j++) if ((i & j) == 0) ret = (ret + ans[i][j]) % Mod; cout << ret << "\n"; return 0; }