「動態規劃::狀壓DP」網格圖遞推 / AcWing 292|327(C++)

目錄

概述

相鄰行遞推

思路

算法過程

優化方案

空間優化

返回值優化

Code

復雜度

相鄰兩行遞推

思路

算法過程

Code

復雜度

特殊優化:編譯期計算

總結


概述

如果我們有一張地圖,要求是在符合某類條件的前提在地圖上放置最優解,該怎么計算?

這也屬于狀壓dp的應用之一。


相鄰行遞推

AcWing 237:

農夫約翰的土地由?M×N 個小方格組成,現在他要在土地里種植玉米。

非常遺憾,部分土地是不育的,無法種植。

而且,相鄰的土地不能同時種植玉米,也就是說種植玉米的所有方格之間都不會有公共邊緣。

現在給定土地的大小,請你求出共有多少種種植方法。

土地上什么都不種也算一種方法。

輸入格式

第?1?行包含兩個整數?M?和?N。

第?2..M+1 行:每行包含?N?個整數?0?或?1,用來描述整個土地的狀況,1?表示該塊土地肥沃,0?表示該塊土地不育。

輸出格式

輸出總種植方 1e8 取模后的值。

數據范圍

1≤M,N≤12

輸入樣例:

2 3
1 1 1
0 1 0

輸出樣例:

9

思路

之所以可以進行狀壓dp是因為列的數量較小,我們可以把一行壓縮成一個整數。

也就是說我們有地圖狀態 g[i] = (10011)^{_{2}},?意味著第 i 行的第0、1、4列可以種植。

我們發現需要兩個狀態:當前行序號和當前行內種植的情況。

定義 dp[i][k],表示[0,?i] 行內且第 i 行狀態為 k 的種植方案數,例如:

若 k =?(10001)^{_{2}},意味著第 i 行的種植了第0、4列。

那么dp[i][k]可以從什么轉移來呢?我們發現有以下約束:

  1. k 要服從地圖的約束,也就是第 i 行的合法 k 僅能是 g[i] 的子集。
  2. k 內不含有相鄰1,也就是行內需要合法。
  3. 第 i 行 k 也不能與 i - 1 行 k 不含有相鄰1,也就是行間需要合法。

基于此,我們可以得知轉移方程: dp[i][k] =?\sumdp[i - 1][t]。

其中,k、t 都行內合法且符合地圖約束,且 k 與 t 行間兼容。?

初始條件下,dp[0][服從地圖0行的k] = 1。

算法過程

可以預處理出合法的 k。

行內合法的條件是:?(k & (k >> 1) == 0。什么意思呢?k 的二進制位右移1位,與原來的 k 作與運算,就是試圖讓相鄰的1重疊在一起,若真的有相鄰的1,那么與運算結果必不為0。

為什么不用考慮左移呢?因為對于相鄰的11,右1左移和左1右移是一樣的。

如果你糾結于右移的溢出位的話,可以將二進制串前后補上無限長的0,再感受一下。

符合地圖約束的k的條件:(k | g[i]) == g[i]。意味著k是g[i]的子集。
行間合法的條件是:(k1 & k2) == 0。意味著二者無重合的1,作為上下行時,就沒有相鄰的1。

那代碼大概是這樣的:?

int solve(){for (int k = 0; k <= mx; k++)if (!(k & (k >> 1)))st.push_back(k);for (int k : st)if ((k | g[0]) == g[0])dp[0][k] = 1;for (int i = 1; i < m; i++)for (int k : st) if((k | g[i]) == g[i])for (int pre_k : st) if (!(k & pre_k) && (pre_k | g[i - 1]) == g[i - 1])dp[i][k] = (dp[i][k] + dp[i - 1][pre_k]) % MOD;int ans = 0;for (int k : st)ans = (ans + dp[m - 1][k]) % MOD;return ans;
}

優化方案

空間優化

注意到dp[i]總是來自上一行dp[i - 1],所以可以做一層空間優化。

僅使用兩行儲存dp,怎么區分哪行是上一行,哪行是這一行呢?

注意到枚舉 i 時, i 的奇偶性持續發生變化,我們用 i & 1 作索引,dp[i & 1]儲存第 i 行,dp[(i - 1) & 1] 就是上一行,當開始計算dp[i & 1][k]時,要先置 0 ,清除上上行的狀態。

返回值優化

在最后,我們選擇 i 多枚舉1行,即計算到 i == m,此時答案就儲存在?dp[m & 1][0]中,即 m 行,那個不存在的行,放置情況是0時即全空,這時的總狀態。

Code

#include <bits/stdc++.h>
using namespace std;
constexpr int N = 13, MOD = 1e8;
int m, n, mx;
int g[N], dp[2][1 << N];
vector<int> st;
int solve(){for (int k = 0; k <= mx; k++)if (!(k & (k >> 1)))st.push_back(k);for (int k : st)if ((k | g[0]) == g[0])dp[0][k] = 1;for (int i = 1; i <= m; i++)for (int k : st) if((k | g[i]) == g[i]) {dp[i & 1][k] = 0;for (int pre_k : st) if (!(k & pre_k) && (pre_k | g[i - 1]) == g[i - 1])dp[i & 1][k] = (dp[i & 1][k] + dp[(i - 1) & 1][pre_k]) % MOD;}return dp[m & 1][0];
}
int main(){cin >> m >> n;  mx = (1 << n) - 1;for (int i = 0; i < m; i++)for (int j = 0; j < n; j++) {int x; cin >> x;g[i] |= x << j;}cout << solve();return 0;
}

復雜度

時間復雜度:?O(?+?m(\frac{1 + \sqrt{5}}{2}) ^{n})

空間復雜度:?O(2^{n})?

n:列數

m:行數

復雜度分析

時間分析:{

枚舉合法 k ,O()。

求合法 k 的數目:n 位不含有連續1的二進制數,設為 cnt(n)。

設 a[n] 表示 n 位不含有連續1且末位為1的二進制數數目,b[n] 表示 n 位不含有連續1且末位為0的二進制數數目。

則 cnt(n) = a[n]?+ b[n]。

其中 a[n] =?b[n - 1],b[n] = a[n - 1] + b[n - 1] = cnt(n - 1)。(前位為1則末位為0,前位為0則末尾任意)

則 cnt(n) = a[n]?+ b[n] = b[n - 1]+ a[n - 1] + b[n - 1] = cnt(n - 1) + b[n - 1] = cnt(n - 1) +? cnt(n - 2)。

此乃斐波那契數列。考慮 cnt(1) = 2 = fib(3),則 cnt(n) = fib(n + 2)。

fib(n)通項公式:

n趨于無窮大時,cnt(n) =??\frac{1}{\sqrt{5}}(\frac{1 + \sqrt{5}}{2}) ^{n + 2}

共有m行,每行枚舉兩遍k,忽略常數項,O(m(\frac{1 + \sqrt{5}}{2}) ^{n})。

總時間復雜度??O(?+?m(\frac{1 + \sqrt{5}}{2}) ^{n})。

}?


相鄰兩行遞推

AcWing 327:

司令部的將軍們打算在?N×M 的網格地圖上部署他們的炮兵部隊。

一個?N×M 的地圖由?N 行?M?列組成,地圖的每一格可能是山地(用?H?表示),也可能是平原(用?P?表示),如下圖。

在每一格平原地形上最多可以布置一支炮兵部隊(山地上不能夠部署炮兵部隊);一支炮兵部隊在地圖上的攻擊范圍如圖中黑色區域所示:

1185_1.jpg

如果在地圖中的灰色所標識的平原上部署一支炮兵部隊,則圖中的黑色的網格表示它能夠攻擊到的區域:沿橫向左右各兩格,沿縱向上下各兩格。

圖上其它白色網格均攻擊不到。

從圖上可見炮兵的攻擊范圍不受地形的影響。

現在,將軍們規劃如何部署炮兵部隊,在防止誤傷的前提下(保證任何兩支炮兵部隊之間不能互相攻擊,即任何一支炮兵部隊都不在其他支炮兵部隊的攻擊范圍內),在整個地圖區域內最多能夠擺放多少我軍的炮兵部隊。

輸入格式

第一行包含兩個由空格分割開的正整數,分別表示?N?和?M;

接下來的?NN?行,每一行含有連續的?M?個字符(P?或者?H),中間沒有空格。按順序表示地圖中每一行的數據。

輸出格式

僅一行,包含一個整數?K,表示最多能擺放的炮兵部隊的數量。

數據范圍

N≤100,M≤10

輸入樣例:

5 4
PHPP
PPHH
PPPP
PHPP
PHHP

輸出樣例:

6

這次變成了兩行遞推。?

思路

只需要對上次的代碼稍作修改。

dp[i & 1][k1][k2]?表示 [0,?i] 行且第 i 行狀態為 k1,第 i - 1 行狀態為 k2 的最值。

三個約束:

  1. k 要服從地圖的約束,也就是第 i 行的合法 k 僅能是 g[i] 的子集。
  2. k 內不含有相鄰1或相間一個0,也就是行內需要合法。
  3. 第 i 行 k 也不能與 i - 1 行和 i - 2k 不含有相鄰1,也就是兩行間需要合法。

算法過程

行內合法的條件是:?(k & (k >> 1) == 0 && ?(k & (k >> 2) == 0。

符合地圖約束的k的條件:(k | g[i]) == g[i]。
行間合法的條件是:(k1 & k2) == 0 && (k1 & k3) == 0?&& (k2 & k3) == 0 。

Code

#include <bits/stdc++.h>
using namespace std;
constexpr int N = 100, M = 10, MOD = 1e8;
int m, n, mx;
int g[N], dp[2][1 << M][1 << M], cnt[1 << M];
vector<int> st;
bool check(int k){return !(k & (k >> 1)) && !(k & (k >> 2));
}
bool check(int a, int b){return !(a & b);
}
int solve(){for (int k = 0; k <= mx; k++)if (check(k)) {st.push_back(k);cnt[k] = bitset<M>(k).count();}for (int k1 : st) if ((k1 | g[0])== g[0]) {dp[0][k1][0] = cnt[k1];for (int k2 : st) if ((k2 | g[1])== g[1] && check(k1, k2))dp[1][k2][k1] = cnt[k1] + cnt[k2];}for (int i = 2; i <= n + 1; i++)for (int k1 : st) if ((k1 | g[i]) == g[i])for (int k2 : st) if ((k2 | g[i - 1]) == g[i - 1] && check(k1, k2)) {dp[i & 1][k1][k2] = 0;for (int k3 : st) if ((k3 | g[i - 2]) == g[i - 2] && check(k1, k3) && check(k2, k3))dp[i & 1][k1][k2] = max(dp[i & 1][k1][k2], dp[(i - 1) & 1][k2][k3] + cnt[k1]);}return dp[(n + 1) & 1][0][0];
}
int main(){cin >> n >> m;  mx = (1 << m) - 1;for (int i = 0; i < n; i++)for (int j = 0; j < m; j++) {char x; cin >> x;g[i] |= ((x == 'P') << j);}cout << solve();return 0;
}

復雜度

時間復雜度:?O(?+?mT ^{n})

空間復雜度:?O(2^{n})?

T:常數,1 < T <?\frac{1 + \sqrt{5}}{2}

n:列數

m:行數


特殊優化:編譯期計算

以第一題為例,我們知道 k 可以預處理得到,那能否更進步一些呢??

C++提供大量的靜態計算支持,我們可以利用C++特性做一些工作。

我們直接在編譯期計算 k 的合法狀態,constexpr 函數會嘗試進行編譯期計算,我們傳入編譯期常量 N,就會在編譯期直接生成出合法狀態的數組。

雖然表面上我們是在編譯期調用計算函數,但其實它已經算好了,已經儲存在可執行程序中,這就省去了一個O()。

運行期由于每次數據范圍不同,可以二分得到 k 的上界,然后正常計算即可。

constexpr int N = 13, MOD = 1e8;
int m, n, mx;
int g[N], dp[2][1 << N];
constexpr pair<array<int, 1 << N>, int> get_st(int MX){array<int, 1 << N> st{};int st_size = 0;for (int k = 0; k < MX; k++)if (!(k & (k >> 1)))st[st_size++] = k;return {st, st_size};
}
constexpr auto s = get_st(1 << N);
constexpr auto st = s.first;
constexpr auto st_size = s.second;
int solve(){int upper = upper_bound(st.begin(), st.begin() + st_size, mx) - st.begin();for (int k = 0; k < upper; k++)if ((st[k] | g[0]) == g[0])dp[0][st[k]] = 1;for (int i = 1; i <= m; i++)for (int k = 0; k < upper; k++) {dp[i & 1][st[k]] = 0;for (int pre_k = 0; pre_k < upper; pre_k++)if (!(st[k] & st[pre_k]) && (st[k] | g[i]) == g[i])dp[i & 1][st[k]] = (dp[i & 1][st[k]] + dp[(i - 1) & 1][st[pre_k]]) % MOD;}return dp[m & 1][0];
}

總結

這就是狀壓DP的又一實例:網格圖遞推,后續將講解利用狀壓DP的更復雜度的遞推類問題。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉載,請注明出處:https://dhexx.cn/hk/5529945.html

如若內容造成侵權/違法違規/事實不符,請聯系我的編程經驗分享網進行投訴反饋,一經查實,立即刪除!


相關文章:

  • 【QT】理解QT機制之“元對象系統”
  • 【博客系統】博客系統第十一彈:從零開始在 Linux 系統上搭建 Java 部署環境并部署 Web 項目
  • 人工智能在智能零售中的創新應用與未來趨勢
  • thinkphp 5.1 部分知識記錄<一>
  • Axure設計案例——科技感漸變線性圖
  • RuoYi前后端分離框架集成手機短信驗證碼(一)之后端篇
  • WPF 按鈕點擊音效實現
  • Qt DateTimeEdit(時間?期的微調框)
  • 本地部署大模型llm+RAG向量檢索問答系統 deepseek chatgpt
  • 汽車制造場景下Profibus轉Profinet網關核心功能與應用解析
  • ubuntu 安裝上傳的 ffmpeg_7.1.1.orig.tar.xz并使用
  • 2025年- H56-Lc164--200.島嶼數量(圖論,深搜)--Java版
  • 【Redis】第3節|深入理解Redis線程模型
  • Axios 如何通過配置實現通過接口請求下載文件
  • Unity Button 交互動畫
  • SQL的查詢優化
  • go并發編程| channel入門
  • [預訓練]Encoder-only架構的預訓練任務核心機制
  • 題目 3298: 藍橋杯2024年第十五屆決賽真題-兔子集結
  • 基于隨機函數鏈接神經網絡(RVFL)的鋰電池健康狀態(SOH)預測
  • 霹靂吧啦Wz_深度學習-圖像分類篇章_1.1 卷積神經網絡基礎_筆記
  • Vision Transformer網絡結構
  • 【Python Cookbook】迭代器與生成器(四)
  • CSS篇-1
  • 在 Ubuntu 上安裝 NVM (Node Version Manager) 的步驟
  • 基于通義千問的兒童陪伴學習和成長的智能應用架構。
  • Go語言中flag包的用法詳解
  • DFS:從入門到進階的刷題指南
  • MATLAB 橫向剪切干涉系統用戶界面設計及其波前重構研究
  • 各國競爭的下一代液晶技術:中國鐵電液晶取得重大突破突破
  • 為什么在我的Flask里面有兩個路由,但是在網頁里有一個卻不能正確訪問到智能體
  • 花哨桌面 V 3.0.0 (火影忍者版)
  • leetcode450.刪除二叉搜索樹中的節點:遞歸法利用有序性處理四種刪除場景
  • Vue-06(“$emit”和事件修飾符)
  • 《勝算》
  • 初學python的我開始Leetcode題10-1
  • 阿里通義實驗室突破空間音頻新紀元!OmniAudio讓360°全景視頻“聲”臨其境
  • siglip2(2) Naflex模型的動態分辨率原理
  • Java開發經驗——阿里巴巴編碼規范實踐解析6
  • 4. Qt對話框(2)
  • Spring Boot 3.5.0中文文檔上線
  • Windows Server 2019--10 網絡地址轉換
  • 能源領域新興技術論壇:EMQ 實時數據引擎構建工業智能中樞
  • Java 微服務架構設計:服務拆分與服務發現的策略
  • 【AI論文】論文轉海報:邁向從科學論文到多模態海報的自動化生成
  • 桂花網體育運動監測方案:開啟幼兒園運動健康管理新篇章
  • 進程控制與調度下
  • STM32 HAL庫函數學習 GPIO篇
  • 計算機視覺---YOLOv4
  • 【stm32開發板】原理圖設計(電源部分)附:設計PCB流程