1. 程式人生 > 其它 >2020ICPC濟南站L-Bit Sequence(數位dp)

2020ICPC濟南站L-Bit Sequence(數位dp)

技術標籤:演算法動態規劃

題目連結

題目思路:

題目條件比較繁雜,一步一步分析.

1.要求同時滿足 m m m個條件,那麼遞迴出口就不是 O ( 1 ) O(1) O(1)的判斷,而是 O ( m ) O(m) O(m)迴圈判斷.

2.需要讓我們判斷的等式為 f ( x + i ) ≡ a i ( m o d 2 ) f(x+i) \equiv a_i \ \ (mod \ \ 2) f(x+i)ai(mod2)

2.1這個式子涉及到了加法,就可能有進位,比較難處理。但是發現 i i i比較小,即只會對數 L L L低六位產生影響。所以可以暴力列舉低六位的情況。

2.2考慮來自低六位的進位會對【二進位制中1】的個數的影響:

記錄一個從第七位開始的連續的1的個數為 o n e one one.那麼 o n e → 1 one \rightarrow 1 one1

所以若進位: f ( x + i ) ≡ s u m − o n e + f ( ( x + i ) & ( 1 < < 8 ) ) ( m o d 2 ) f(x + i) \equiv sum - one+f((x + i)\&(1 << 8)) \ \ (mod \ \ 2) f(x+i)sumone+f((x+i)&(1<<8))(mod2)

不進位時: f ( x + i ) ≡ s u m + f ( ( x + i ) & ( 1 < < 7 ) ) ( m o d 2 ) f(x + i) \equiv sum +f((x + i)\&(1 << 7)) \ \ (mod \ \ 2)

f(x+i)sum+f((x+i)&(1<<7))(mod2)

然後我們知道二進位制下加法減法都是異或。所以統一按異或處理即可.

所以總體上 d p ( 前 綴 長 度 , 數 位 和 的 奇 偶 , 當 前 連 續 1 的 個 數 的 奇 偶 , 頂 到 上 界 ) dp(字首長度,數位和的奇偶,當前連續1的個數的奇偶,頂到上界) dp(,,1,)

然後 d f s dfs dfs到後六位時直接暴力統計即可.

注意點:

1.暴力統計的時候注意,若頂到上界,那麼上界就應該是 L % 128 L\%128 L%128,否則是 2 7 − 1 2^7-1

271

2.還有記憶化搜尋的時候注意由於遞迴出口不再是 O ( 1 ) O(1) O(1)的複雜度,而是 O ( 2 7 ∗ m ) O(2^7*m) O(27m),所以得優先記憶化返回,而不是遞迴出口在前。

時間複雜度:

複雜度分為兩個部分,一個部分是記憶化搜尋填表, O ( d p 數 組 大 小 ∗ 轉 移 ) = O ( 16 ∗ l o g L ) O(dp陣列大小*轉移)=O(16*logL) O(dp)=O(16logL)

一部分是後六位的暴力計算 c a l 函 數 cal函式 cal,根據狀態個數,它最多被呼叫 8 8 8次.每次複雜度為: O ( 2 7 ∗ m ) O(2^7*m) O(27m).總複雜度為: O ( 2 10 ∗ m ) O(2^{10}*m) O(210m)

整個演算法總複雜度為: O ( T ( 16 ∗ l o g L + 2 10 ∗ m ) ) O(T(16*logL+2^{10}*m)) O(T(16logL+210m))

最差計算次數跑到 1 e 8 1e8 1e8次左右,但是實際執行 15 m s 15ms 15ms

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
const int maxn = 100 + 5;
const int mod = 1e9 + 7;
int a[maxn] , m , dig[maxn] , cnt;
ll dp[65][2][2][2] , L;
int f[500];
ll cal (int sum , int one , int li)
{
    int s = (li ? L % 128 : 127);
    int ans = 0;
    for (int i = 0 ; i <= s ; i++){
        bool ok = true;
        for (int j = 0 ; j < m && ok ; j++){
            if (i + j < 128) ok = ((f[i + j] ^ sum) == a[j]);
            else ok = ((f[i + j] ^ one ^ sum) == a[j]);
        }
        ans += ok;
    }
    return ans;
}
ll dfs (int step , int sum , int one , int li) {
    ll &x = dp[step][sum][one][li];
    if (~x) return x;
    if (step <= 6){
        x = cal(sum , one , li);
        return x;
    }
    int up = (li ? dig[step] : 1);
    ll ans = 0;
    for (int i = 0 ; i <= up ; i++){
        int no = one;
        if (i) no ^= 1;
        else no = 0;
        ans += dfs(step - 1 , sum ^ i , no , li && i == up);
    }
    return x = ans;
}
ll solve (ll x)
{
    memset(dp , -1 , sizeof dp);
    cnt = 0;
    int len = 0;
    for (int i = 63 ; i >= 0 ; i--){
        dig[i] = (x >> i) & 1;
        if (dig[i]) len = max(len , i);
    }
    return dfs(len , 0 , 0 , 1);
}
int main()
{
    ios::sync_with_stdio(false);
    int t; cin >> t;
    for (int i = 1 ; i < 500 ; i++)
        f[i] = (f[i >> 1] + (i & 1))%2;
    while (t--){
        cin >> m; cin >> L;
        for (int i = 0 ; i < m ; i++) cin >> a[i];
        cout << solve (L) << endl;
    }
    return 0;
}