luoguP5023 NOIP2018 填數遊戲 狀壓dp 找規律
阿新 • • 發佈:2018-12-09
題意
- 給你一個 的矩陣,在每個位置填上 和 ,使得不存在兩條路徑一條路徑先往下走的位置構成的字串字典序小魚先後往下走的位置構成的字串,求方案數。
這個題 的時候找規律拿了 還是價效比蠻高…
寫出正解規律關鍵在於寫出 的暴力
首先有兩個性質
如果 則 的右下角所有對角線元素都相同
每條對角線從左下標號為 的話是單調不上升的
這兩個性質可以簡單推導證明
那麼我們可以寫出一個跟對角線有關的狀壓
設 為正在處理第 條對角線,上面放了 個 ,這條對角線上保證右下角對角線元素都相同的點集為 ,那麼每次暴力列舉放多少個 ,轉移到的集合 ,可以由 集合推出,每次找到每個點右邊和下面那個點看能不能滿足,直接 就可以了,複雜度
這個暴力可以跑蠻多資料
然後我們可以發現
那麼我們在 的時候跑dp算
否則就跑 的dp 再乘上 就可以了
Codes
#include <bits/stdc++.h>
using namespace std;
const int mod = 1e9 + 7;
int f[18][9][1 << 8], len[18], n, m, ans = 1;
bool inset(int x, int S) {return (1 << (x - 1)) & S;}
int down(int x, int y) {return y - (x <= m);}
int right(int x, int y) {return y + (x > m);}
int DP() {
int num = n + m - 1;
for (int i = 1; i <= m; ++ i)
len[i] = min(i, n);
for (int i = 1; i < n; ++ i)
len[i + m] = n - i;
f[1][0][1] = f[1][1][1] = 1;
for (int i = 2; i <= num; ++ i) {
int all = 1 << len[i - 1];
for (int j = 0; j <= len[i - 1]; ++ j)
for (int S = 0; S < all; ++ S)
if (f[i - 1][j][S]) for (int k = 0; k <= len[i]; ++ k) {
int flag = true, T = 0;
for (int r = 1; r < len[i]; ++ r)
if (r != k && !inset(right(i, r), S)) {
flag = false; break;
}
if (flag) {
for (int r = 1; r <= len[i]; ++ r) {
int D = down(i, r), R = right(i, r);
if (!D && inset(R, S)) T |= 1 << (r - 1);
else if (R > len[i - 1] && inset(D, S)) T |= 1 << (r - 1);
else if (r != j && inset(D, S) && inset(R, S)) T |= 1 << (r - 1);
}
(f[i][k][T] += f[i - 1][j][S]) %= mod;
}
}
}
return ((f[num][0][1] + f[num][1][1]) % mod + (f[num][0][0] + f[num][1][0]) % mod) % mod;
}
int main() {
#ifdef ylsakioi
freopen("5023.in", "r", stdin);
freopen("5023.out", "w", stdout);
#endif
scanf("%d%d", &n, &m);
if (n > m) swap(n, m);
if (n == m || m <= 8) ans = DP();
else {
for (; m > n + 1; -- m)
ans = 3ll * ans % mod;
ans = 1ll * ans * DP() % mod;
}
printf("%d\n", ans);
return 0;
}