1. 程式人生 > 實用技巧 >填數遊戲

填數遊戲

這是一道 打表\(+\)亂搞 題,目前只會打表和亂搞做法,以後水品提升了不知道能不能作出證明或者寫出狀壓??

首先我們可以發現 \(n \le 3\) 的情況有 \(65pts\),而 \(n\) 這麼小,打一下表何樂而不為呢?於是我寫了一個爆枚每個位置再 \(check\)\(dfs\),複雜度 \(O(2 ^ {nm + n + m} \log 2 ^ {n + m - 1})\) 因為 \(n\) 實在太小了於是我打出了下面的表(\(1\) 的情況直接算就沒打了,下面第一個數都是從 \(m = n\) 開始的)

2 : 12 36 108 324 ...
3 : 112 336 1008 3024 ...

很明顯可以發現這裡每個答案往後都乘了 \(3\),於是我們只需要寫一個快速冪就可以獲得 \(65pts\) 的高分。之後我算出了 \((4, 4)\) 的答案 \(912\),又算了 \((4, 5)\) 的答案想要驗證乘 \(3\) 的猜想,但 \((4, 5)\) 卻是 \(2688 \ne 3 \times 912\),然後我再想跑出 \((4, 6)\) 的答案,可是目前的 \(dfs\) 大概要跑 \(2h\) 左右,於是我再分析了一下樣例,發現實際上題目的要求是優先往右走的路徑的 \(01\) 字典序要不大於優先往下走的所有路徑 \(01\) 字典序,因此我有了這樣一個剪枝方法,我們先暴力列舉第一列的所有數,然後從最後一行開始暴力列舉,吧每一條路徑壓乘十進位制數在樹狀陣列上修改,那麼再一遍遍爆枚上一層,記錄一下每個位置到終點的最大 \(01\)

字典序加到當前的字典序後面然後再線上段樹上查詢是否有小於當前字典序的串,如果有剪枝即可。這個東西看起來很難實現,而且複雜度也很玄學,回頭想一下題目的條件,要求優先往右走的字典序小,那麼假設現在有兩個 \(01\)\(a, b\) 前者優先往下走,後者優先往右走,假設目前兩串相同,如果下面 \(a < b\) 那麼後者必然走到 \(1\) 而前者必然走到 \(0\),再注意到 \(a, b\) 長度應該相等也就意味著兩者走的步數相同,那麼兩者應該都在左下到右上的一條對角線上,因此我們可以得出一個結論,每一條左下到右上的對角線都是先一段 \(1\) 後一段 \(0\)。那麼我們就可以有了一種新的爆搜方式,我們爆枚每個對角線 \(0, 1\)
分界點,再暴力 \(check\) 這樣複雜度是 \(O((m!) ^ 2 2 ^ {n + m} \log 2 ^ {n + m})\),算了一下這東西能跑出 \((6, 6)\),於是我實現了這個 \(dfs\),發現 \((6, 6)\) 只需要 \(15s\),於是我讓他去跑 \((7, 7)\) 打出了 \(n \le 6\) 的表。

2 : 12 36 108 324 972 2916 ...
3 : 112 336 1008 3024 9072 27216 ...
4 : 912 2688 8064 24192 72576 217728 ...
5 : 7136 21312 63936 191808 ...
6 : 56768 170112 510336 ...

可以發現,雖然 \((4, 4) \rightarrow (4, 5)\) 不是乘 \(3\),但 \((4, 5) \rightarrow (4, 6), (4, 6) \rightarrow (4, 7)\) 接下來都是乘 \(3\),對於 \(5, 6\) 也是一樣。於是我們大膽地猜測剩下的所有情況應該都是從第二項開始乘 \(3\) 的。那我們只需要求出第一項和第二項即可。

過了 \(20min\) 終於跑出了 \((7, 7)\),現在的表如下:

2 : 12 36 108 324 972 2916 ...
3 : 112 336 1008 3024 9072 27216 ...
4 : 912 2688 8064 24192 72576 217728 ...
5 : 7136 21312 63936 191808 ...
6 : 56768 170112 510336 ...
7 : 453504 ...

感覺 \((7, 8)\) 應該是死都跑不出來了,但是我們打表出了這麼多資料,可以找一下規律。我們發現 \(n \ge 4\) 時雖然第一項乘 \(3\) 不等於第二項,但隔得非常近,於是我算出了 \(n = 4\)\(912 \times 3 - 2688 = 48\),接著再算了 \(n = 5\)\(7136 \times 3 - 21312 = 96\),然後又算了 \(n = 6\)\(56768 \times 3 - 170112 = 192\),規律非常明顯了,每次插值乘了 \(2\),並且可以發現 \(48 = 3 \times 2 ^ 4, 96 = 3 \times 2 ^ 5\),於是我們可以發現 \((n, n + 1) = 3 \times (n, n) - 3 \times 2 ^ n\),那麼我們只需要跑出 \((8, 8)\) 這題就做完了,然而貌似 \((8, 8)\) 根本跑不出...

應該還有規律,繼續觀察。可以發現 \(m \ge n + 2\) 的情況都是從 \((n, n + 1)\) 推來的,而 \((n, n + 1)\)\((n, n)\) 有關,於是我們可以考慮只觀察前面兩項,觀察一段時間後可以發現 \(((n, n) + (n, n + 1)) \times 2\)\((n + 1, n + 1)\) 非常接近,於是我又算了他們的差值可以發現 \(4 \rightarrow 5 = 64, 5 \rightarrow 6 = 128, 6 \rightarrow 7 = 256\),規律也非常明顯,每次乘了 \(2\),並且可以發現每次的插值 \(i - 1 \rightarrow i = 2 ^ {i + 1}\),那麼我們豈不是知道前兩項的遞推式了?令 \(f_{i, j}\)\((i, j)\) 的答案,於是我們有 \(f_{i, i} = (f_{i - 1, i - 1} + f_{i - 1, i}) \times 2 - 2 ^ {i + 1}, f_{i, i + 1} = 3 \times f_{i, i} - 3 \times 2 ^ i\),於是我們直接矩陣快速冪遞推出前兩項,後面的直接使用快速冪即可。

#include<bits/stdc++.h>
using namespace std;
#define N 5
#define Mod 1000000007
#define rep(i, l, r) for(int i = l; i <= r; ++i)
struct mat{
    int g[N][N];
    void clear(){ memset(g, 0, sizeof(g));}
}f, tr, ans;
int n, x, m;
int read(){
    char c; int x = 0, f = 1;
    c = getchar();
    while(c > '9' || c < '0'){ if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
int Inc(int a, int b){
    return (a += b) >= Mod ? a - Mod : a;
}
int Dec(int a, int b){
    return (a -= b) < 0 ? a + Mod : a;
}
int Mul(int a, int b){
    return 1ll * a * b % Mod;
}
int Qpow(int a, int b){
    int ans = 1;
    while(b){
        if(b & 1) ans = Mul(ans, a);
        a = Mul(a, a), b >>= 1;
    }
    return ans;
}
mat mul(mat a, mat b){
    mat c; c.clear();
    rep(i, 1, 3) rep(j, 1, 3) rep(k, 1, 3) c.g[i][j] = Inc(c.g[i][j], Mul(a.g[i][k], b.g[k][j]));
    return c;
}
int main(){
    n = read(), m = read(); if(n > m) swap(n, m);
    if(n == 1) printf("%d", Qpow(2, m));
    else if(n == 2) printf("%d", Mul(12, Qpow(3, m - 2)));
    else if(n == 3) printf("%d", Mul(112, Qpow(3, m - 3)));
    else{
        f.g[1][1] = 912, f.g[1][2] = 2688, f.g[1][3] = 32;
        tr.g[1][1] = 2, tr.g[1][2] = 6, tr.g[1][3] = 0;
        tr.g[2][1] = 2, tr.g[2][2] = 6, tr.g[2][3] = 0;
        tr.g[3][1] = -2, tr.g[3][2] = -9, tr.g[3][3] = 2;
        ans.g[1][1] = ans.g[2][2] = ans.g[3][3] = 1;
        x = n - 4;
        while(x){
            if(x & 1) ans = mul(ans, tr);
            tr = mul(tr, tr), x >>= 1;
        }
        f = mul(f, ans);
        if(n == m) printf("%d", f.g[1][1]);
        else printf("%d", Mul(f.g[1][2], Qpow(3, m - n - 1)));
    }
    return 0;
}