觀前須知 本題解使用 CC BY-NC-SA 4.0 許可。 同步發(fā)布于 Luogu 題解區(qū)。 更好的觀看體驗 請點這里。 筆者的博客主頁 正文 Luogu P3007 【USACO11JAN】 The Continental Cowngress G 前置知識:2-SAT、Tarjan。 (應(yīng)該沒有
本題解使用
CC BY-NC-SA 4.0 許可
。
同步發(fā)布于
Luogu
題解區(qū)。
更好的觀看體驗
請點這里
。
筆者的博客主頁
Luogu P3007 【USACO11JAN】 The Continental Cowngress G
前置知識:
2-SAT
、
Tarjan
。
(應(yīng)該沒有人會 2-sat 不會 tarjan 吧)
這道題除去輸出 '?' 的部分是一道 2-SAT 的純版子題。
其他題解基本使用了 DFS 來判斷該情況。
這里提供一份
bitset
優(yōu)化的
DAG DP
解法。
憑借著 bitset 強大的優(yōu)化,成功跑到 34ms,輕松 rk1。
由于這道題的前半部分就是 2-SAT 的板子題,所以不再過多贅述。
感興趣可見
Luogu P4782 【模板】2-SAT
。
那么考慮什么情況下可選也可不選,
發(fā)現(xiàn)當(dāng)且僅當(dāng)
\(x\)
(表示法案
\(x\)
通過)和
\(x'\)
(表示法案
\(x\)
不通過)不連通時可選可不選。
因為我們已經(jīng)有了一個 DAG,可以求出它的
拓撲序
,
那么不連通也就是拓撲序
靠后
的那一個節(jié)點的
祖先
中沒有它的反節(jié)點。
強連通分量題做得多的 dalao 們應(yīng)該已經(jīng)想到這道題可以 DP 了。
小 trick:
Tarjan 求出的強連通分量編號是縮點后的圖的 拓撲逆序 。
從 Tarjan 遞歸過程的角度思考一下很好證明。
……
不賣關(guān)子啦。
其實就是在搜索樹上,由于遞歸類似于 棧 ,是 先進后出 的,
所以一個節(jié)點后代節(jié)點可在搜索樹上后搜到,回溯時,后代節(jié)點先被統(tǒng)計強連通分量。
所以后代節(jié)點一定在祖先節(jié)點之前被統(tǒng)計,得到的順序就是拓撲逆序了。
具體的 DP 方式是這樣的:
縮點后,對于每個節(jié)點(這里是原圖的強連通分量),維護一個
\(vis_u\)
數(shù)組,
\(vis_{u,v}=1\)
表示
\(v\)
在
\(u\)
的祖先節(jié)點中出現(xiàn)過,反之表示未出現(xiàn)過。
轉(zhuǎn)移就是如果
\(v\)
在
\(u\)
的祖先節(jié)點出現(xiàn)過,則它肯定在
\(son(u)\)
的祖先節(jié)點中出現(xiàn)過。
特別地,
\(vis_{u,u}=1\)
。
具體轉(zhuǎn)移方式見代碼:
// co[0] 存儲強連通分量個數(shù)
// 這里使用了強連通分量是拓撲逆序的小 trick
for (int u = co[0]; u; u--) {
vis[u][u] = true;
for (int i = head[u]; i; i = e[i].nxt)
for(int j = 1; j <= co[0]; j++)
vis[e[i].v][j] |= vis[u][j];
}
那么如果一個法案可選也不可選,即不連通時有:
\(vis_{u,v}=0\)
,其中
\(u\)
為拓撲序靠后的節(jié)點,
\(v\)
為
\(u\)
的反節(jié)點
判斷的具體代碼如下:
for (int i = 1; i <= n; i++) {
// i 表示通過,i+n 表示不通過
// 判斷拓撲序靠后的是哪個節(jié)點,vx=0 為 i,vx=1 為 i+n
// co[u] 表示 u 節(jié)點所屬的強連通分量
vx = co[i] > co[i + n];
// 利用三目運算簡化語句
// 若有祖先關(guān)系則輸出 Y 或 N
if (vis[co[vx ? i + n : i]][co[vx ? i : i + n]]) putchar(vx ? 'N' : 'Y');
// 否則輸出 ?
else putchar('?');
}
好的那么現(xiàn)在我們已經(jīng)有了一個
\(O(n^2)\)
的算法了。
但是還不夠。
接下來我們來介紹今天的主角:
bitset
。
我們知道,平常我們使用 bool 數(shù)組的時候,像是
bool vis[N];
是要使用
\(8N\)
bit 的空間的。
然而一個 bool 實際上也就是一個 0/1 的值,能不能只占
\(1\)
bit 空間呢?
可以!我們寫成
bitset
就可以了!
這里的
N
是 bitset 的位數(shù),要求必須是整型常量。
bitset 同 bool 數(shù)組一樣,支持形如
vis[u]
的訪問和賦值。
然而它同時支持與、或、左/右移等操作。
可以理解為 bitset 是一個大的二進制數(shù)。
由于篇幅有限,bitset 的詳細用法不會過多贅述,這里只介紹用來優(yōu)化的部分。
更多內(nèi)容可見這篇文章:
C++ std::bitset
。
等一下,它支持或?
我們發(fā)現(xiàn),我們程序中有這么一個操作:
for(int j = 1; j <= co[0]; j++)
vis[e[i].v][j] |= vis[u][j];
這個操作相當(dāng)于把
vis[e[i].v]
這個數(shù)組
整體或
上一個
vis[u]
。
那么我們就可以用 bitset 優(yōu)化了!
開一個
bitset
的數(shù)組,
M
表示強連通分量個數(shù)上限,則我們的程序可以改寫為:
bitset b[N];
for (int u = co[0]; u; u--) {
b[u][u] = true;
// 利用 bitset 的或操作快速轉(zhuǎn)移
for (int i = head[u]; i; i = e[i].nxt) b[e[i].v] |= b[u];
}
/*...*/
// 因為 bitset 支持類似 bool數(shù)組 的操作,所以這段代碼除了一個變量名外沒有變化
for (int i = 1; i <= n; i++) {
vx = co[i] > co[i + n];
if (b[co[vx ? i + n : i]][co[vx ? i : i + n]]) putchar(vx ? 'N' : 'Y');
else putchar('?');
}
那么我們就用 bitset 做完了這道題目。
那么問題來了,優(yōu)化后的空間復(fù)雜度肯定小了,時間復(fù)雜度呢?
直接給結(jié)論:
bitset 的單次整體與、或等操作復(fù)雜度為
\(\mathcal O(\frac{n}{w})\)
。
這里,
\(n\)
指 bitset 的長度,
\(w\)
(不是
\(\omega\)
)為計算機字長,一般為
\(32\)
。
那么整體時間復(fù)雜度就是
\(\mathcal O(\frac{n^2}{w})\)
了,相當(dāng)于在原先的復(fù)雜度上除了一個比較大的
\(\log\)
。
這樣的時間復(fù)雜度就是非常優(yōu)的了。
bitset 優(yōu)化可以優(yōu)化 floyd求傳遞閉包 等算法。
往往 \(10^4\) 的數(shù)據(jù), \(\mathcal O(n^2)\) 無法通過,而 \(\mathcal O(\frac{n^2}{w})\) 就可以通過了。
所以說,bitset 的優(yōu)化真的 非常強大 。
剩下的一些小細節(jié)放在代碼的注釋里了:
本代碼 34ms 的提交記錄
。
#include
using namespace std;
class FD {
private:
inline static int Read() {
int r = 0, f = 0, c = getchar();
while ((c < '0' || c > '9') && ~c) { f |= c == '-', c = getchar(); }
while (c >= '0' && c <= '9') { r = (r << 1) + (r << 3) + (c ^ 48), c = getchar(); }
return f ? -r : r;
}
static constexpr int AwA = 2e3 + 10;
static constexpr int QwQ = 8e3 + 10;
//這里是強連通分量個數(shù),開這么大就能ac
static constexpr int PwP = 360;
struct Edge {
int nxt, v;
} e[QwQ << 1];
int head[AwA << 1], ecnt;
inline void AddEdge(int u, int v) { e[++ecnt] = {head[u], v}, head[u] = ecnt; }
int n, m;
int dfn[AwA], low[AwA], co[AwA], stk[AwA];
bitset b[AwA];
void Tarjan(int u) {
stk[++stk[0]] = u;
dfn[u] = low[u] = ++dfn[0];
for (int i = head[u]; i; i = e[i].nxt) {
int v = e[i].v;
if (!dfn[v]) Tarjan(v), low[u] = min(low[u], low[v]);
else if (!co[v]) low[u] = min(low[u], dfn[v]);
}
if (dfn[u] == low[u]) {
co[u] = ++co[0];
while (stk[stk[0]] != u) co[stk[stk[0]--]] = co[0];
stk[0]--;
}
}
public:
inline void Main() {
n = Read(), m = Read();
int x, y;
bool vx, vy;
//三目運算簡化
while (m--) {
x = Read(), vx = getchar() == 'N';
y = Read(), vy = getchar() == 'N';
AddEdge(vx ? x : x + n, vy ? y + n : y);
AddEdge(vy ? y : y + n, vx ? x + n : x);
}
for (int i = 1; i <= 2 * n; i++) if (!dfn[i]) Tarjan(i);
//判無解
for (int i = 1; i <= n; i++) if (co[i] == co[i + n]) return void(puts("IMPOSSIBLE"));
//建新圖,這里直接用舊圖開新節(jié)點的方式處理了
for (int u = 1; u <= 2 * n; u++)
for (int i = head[u]; i; i = e[i].nxt)
if (co[e[i].v] != co[u]) AddEdge(co[u] + n * 2, co[e[i].v]);
//DAG DP
for (int u = co[0]; u; u--) {
b[u][u] = true;
for (int i = head[u + 2 * n]; i; i = e[i].nxt) b[e[i].v] |= b[u];
}
//輸出答案
for (int i = 1; i <= n; i++) {
vx = co[i] > co[i + n];
if (b[co[vx ? i + n : i]][co[vx ? i : i + n]]) putchar(vx ? 'N' : 'Y');
else putchar('?');
}
putchar('\n');
}
} Fd;
int main() {
Fd.Main();
return 0;
}
完結(jié),撒花!~
機器學(xué)習(xí):神經(jīng)網(wǎng)絡(luò)構(gòu)建(下)
閱讀華為Mate品牌盛典:HarmonyOS NEXT加持下游戲性能得到充分釋放
閱讀實現(xiàn)對象集合與DataTable的相互轉(zhuǎn)換
閱讀算法與數(shù)據(jù)結(jié)構(gòu) 1 - 模擬
閱讀5. Spring Cloud OpenFeign 聲明式 WebService 客戶端的超詳細使用
閱讀Java代理模式:靜態(tài)代理和動態(tài)代理的對比分析
閱讀Win11筆記本“自動管理應(yīng)用的顏色”顯示規(guī)則
閱讀本站所有軟件,都由網(wǎng)友上傳,如有侵犯你的版權(quán),請發(fā)郵件[email protected]
湘ICP備2022002427號-10 湘公網(wǎng)安備:43070202000427號© 2013~2025 haote.com 好特網(wǎng)